Skip to content

Commit bbe3296

Browse files
committed
Rollback 1ac6e37
bpo-39207: Spawn workers on demand in ProcessPoolExecutor (pythonGH-19453) was implemented in a way that introduced child process deadlocks in some environments due to mixing threading and fork() in the parent process.
1 parent c1a41e4 commit bbe3296

3 files changed

Lines changed: 18 additions & 17 deletions

File tree

Doc/whatsnew/3.9.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ Workers in :class:`~concurrent.futures.ProcessPoolExecutor` are now spawned on
404404
demand, only when there are no available idle workers to reuse. This optimizes
405405
startup overhead and reduces the amount of lost CPU time to idle workers.
406406
(Contributed by Kyle Stanley in :issue:`39207`.)
407+
Update: This was reverted in 3.9.11, 3.10.3, and 3.11 as the implementation
408+
could lead to deadlocks. See :issue:`46464`.
407409

408410
curses
409411
------

Lib/concurrent/futures/process.py

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -324,12 +324,6 @@ def run(self):
324324
# while waiting on new results.
325325
del result_item
326326

327-
# attempt to increment idle process count
328-
executor = self.executor_reference()
329-
if executor is not None:
330-
executor._idle_worker_semaphore.release()
331-
del executor
332-
333327
if self.is_shutting_down():
334328
self.flag_executor_shutting_down()
335329

@@ -625,7 +619,6 @@ def __init__(self, max_workers=None, mp_context=None,
625619
# Shutdown is a two-step process.
626620
self._shutdown_thread = False
627621
self._shutdown_lock = threading.Lock()
628-
self._idle_worker_semaphore = threading.Semaphore(0)
629622
self._broken = False
630623
self._queue_count = 0
631624
self._pending_work_items = {}
@@ -661,18 +654,21 @@ def __init__(self, max_workers=None, mp_context=None,
661654
def _start_executor_manager_thread(self):
662655
if self._executor_manager_thread is None:
663656
# Start the processes so that their sentinels are known.
657+
self._adjust_process_count()
664658
self._executor_manager_thread = _ExecutorManagerThread(self)
665659
self._executor_manager_thread.start()
666660
_threads_wakeups[self._executor_manager_thread] = \
667661
self._executor_manager_thread_wakeup
668662

669663
def _adjust_process_count(self):
670-
# if there's an idle process, we don't need to spawn a new one.
671-
if self._idle_worker_semaphore.acquire(blocking=False):
672-
return
673-
674-
process_count = len(self._processes)
675-
if process_count < self._max_workers:
664+
# To get rid of this condition don't fork() from this process.
665+
# This applies to _any_ thread existing in the process at all, but
666+
# that is a long standing issue. We at least make sure this library
667+
# is not the cause of its own deadlocks.
668+
assert not self._executor_manager_thread, (
669+
'Processes cannot be fork()ed after the thread has started, '
670+
'deadlock in the child processes could result; bpo-46464.')
671+
for _ in range(len(self._processes), self._max_workers):
676672
p = self._mp_context.Process(
677673
target=_process_worker,
678674
args=(self._call_queue,
@@ -701,7 +697,6 @@ def submit(self, fn, /, *args, **kwargs):
701697
# Wake up queue management thread
702698
self._executor_manager_thread_wakeup.wakeup()
703699

704-
self._adjust_process_count()
705700
self._start_executor_manager_thread()
706701
return f
707702
submit.__doc__ = _base.Executor.submit.__doc__
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
Remove the 3.11.0a3 Add ``max_tasks_per_child`` from
2-
:class:`concurrent.futures.ProcessPoolExecutor` as the implementation relied
3-
on mixing threads+fork which can cause deadlocks .
1+
Remove the 3.11.0a3 :issue:`44733` added ``max_tasks_per_child`` feature from
2+
:class:`concurrent.futures.ProcessPoolExecutor` as the implementation relied on
3+
mixing threading and fork() which can cause deadlocks in child processes.
4+
5+
Remove the 3.9 :issue:`39207` performance enhancement from that spawned worker
6+
processes for :class:`concurrent.futures.ProcessPoolExecutor` on demand rather
7+
than up front as the implementation could cause deadlocks in the child process.

0 commit comments

Comments
 (0)