@@ -90,6 +90,7 @@ def _python_exit():
90
90
_global_shutdown = True
91
91
items = list (_threads_wakeups .items ())
92
92
for _ , thread_wakeup in items :
93
+ # call not protected by ProcessPoolExecutor._shutdown_lock
93
94
thread_wakeup .wakeup ()
94
95
for t , _ in items :
95
96
t .join ()
@@ -157,8 +158,10 @@ def __init__(self, work_id, fn, args, kwargs):
157
158
158
159
class _SafeQueue (Queue ):
159
160
"""Safe Queue set exception to the future object linked to a job"""
160
- def __init__ (self , max_size = 0 , * , ctx , pending_work_items , thread_wakeup ):
161
+ def __init__ (self , max_size = 0 , * , ctx , pending_work_items , shutdown_lock ,
162
+ thread_wakeup ):
161
163
self .pending_work_items = pending_work_items
164
+ self .shutdown_lock = shutdown_lock
162
165
self .thread_wakeup = thread_wakeup
163
166
super ().__init__ (max_size , ctx = ctx )
164
167
@@ -167,7 +170,8 @@ def _on_queue_feeder_error(self, e, obj):
167
170
tb = traceback .format_exception (type (e ), e , e .__traceback__ )
168
171
e .__cause__ = _RemoteTraceback ('\n """\n {}"""' .format ('' .join (tb )))
169
172
work_item = self .pending_work_items .pop (obj .work_id , None )
170
- self .thread_wakeup .wakeup ()
173
+ with self .shutdown_lock :
174
+ self .thread_wakeup .wakeup ()
171
175
# work_item can be None if another process terminated. In this
172
176
# case, the executor_manager_thread fails all work_items
173
177
# with BrokenProcessPool
@@ -268,17 +272,21 @@ def __init__(self, executor):
268
272
# A _ThreadWakeup to allow waking up the queue_manager_thread from the
269
273
# main Thread and avoid deadlocks caused by permanently locked queues.
270
274
self .thread_wakeup = executor ._executor_manager_thread_wakeup
275
+ self .shutdown_lock = executor ._shutdown_lock
271
276
272
277
# A weakref.ref to the ProcessPoolExecutor that owns this thread. Used
273
278
# to determine if the ProcessPoolExecutor has been garbage collected
274
279
# and that the manager can exit.
275
280
# When the executor gets garbage collected, the weakref callback
276
281
# will wake up the queue management thread so that it can terminate
277
282
# if there is no pending work item.
278
- def weakref_cb (_ , thread_wakeup = self .thread_wakeup ):
283
+ def weakref_cb (_ ,
284
+ thread_wakeup = self .thread_wakeup ,
285
+ shutdown_lock = self .shutdown_lock ):
279
286
mp .util .debug ('Executor collected: triggering callback for'
280
287
' QueueManager wakeup' )
281
- thread_wakeup .wakeup ()
288
+ with shutdown_lock :
289
+ thread_wakeup .wakeup ()
282
290
283
291
self .executor_reference = weakref .ref (executor , weakref_cb )
284
292
@@ -363,6 +371,7 @@ def wait_result_broken_or_wakeup(self):
363
371
# submitted, from the executor being shutdown/gc-ed, or from the
364
372
# shutdown of the python interpreter.
365
373
result_reader = self .result_queue ._reader
374
+ assert not self .thread_wakeup ._closed
366
375
wakeup_reader = self .thread_wakeup ._reader
367
376
readers = [result_reader , wakeup_reader ]
368
377
worker_sentinels = [p .sentinel for p in self .processes .values ()]
@@ -380,7 +389,9 @@ def wait_result_broken_or_wakeup(self):
380
389
381
390
elif wakeup_reader in ready :
382
391
is_broken = False
383
- self .thread_wakeup .clear ()
392
+
393
+ with self .shutdown_lock :
394
+ self .thread_wakeup .clear ()
384
395
385
396
return result_item , is_broken , cause
386
397
@@ -500,7 +511,8 @@ def join_executor_internals(self):
500
511
# Release the queue's resources as soon as possible.
501
512
self .call_queue .close ()
502
513
self .call_queue .join_thread ()
503
- self .thread_wakeup .close ()
514
+ with self .shutdown_lock :
515
+ self .thread_wakeup .close ()
504
516
# If .join() is not called on the created processes then
505
517
# some ctx.Queue methods may deadlock on Mac OS X.
506
518
for p in self .processes .values ():
@@ -619,6 +631,8 @@ def __init__(self, max_workers=None, mp_context=None,
619
631
# _result_queue to send wakeup signals to the executor_manager_thread
620
632
# as it could result in a deadlock if a worker process dies with the
621
633
# _result_queue write lock still acquired.
634
+ #
635
+ # _shutdown_lock must be locked to access _ThreadWakeup.
622
636
self ._executor_manager_thread_wakeup = _ThreadWakeup ()
623
637
624
638
# Create communication channels for the executor
@@ -629,6 +643,7 @@ def __init__(self, max_workers=None, mp_context=None,
629
643
self ._call_queue = _SafeQueue (
630
644
max_size = queue_size , ctx = self ._mp_context ,
631
645
pending_work_items = self ._pending_work_items ,
646
+ shutdown_lock = self ._shutdown_lock ,
632
647
thread_wakeup = self ._executor_manager_thread_wakeup )
633
648
# Killed worker processes can produce spurious "broken pipe"
634
649
# tracebacks in the queue's own worker thread. But we detect killed
@@ -718,12 +733,12 @@ def shutdown(self, wait=True, *, cancel_futures=False):
718
733
with self ._shutdown_lock :
719
734
self ._cancel_pending_futures = cancel_futures
720
735
self ._shutdown_thread = True
736
+ if self ._executor_manager_thread_wakeup is not None :
737
+ # Wake up queue management thread
738
+ self ._executor_manager_thread_wakeup .wakeup ()
721
739
722
- if self ._executor_manager_thread :
723
- # Wake up queue management thread
724
- self ._executor_manager_thread_wakeup .wakeup ()
725
- if wait :
726
- self ._executor_manager_thread .join ()
740
+ if self ._executor_manager_thread is not None and wait :
741
+ self ._executor_manager_thread .join ()
727
742
# To reduce the risk of opening too many files, remove references to
728
743
# objects that use file descriptors.
729
744
self ._executor_manager_thread = None
@@ -732,8 +747,6 @@ def shutdown(self, wait=True, *, cancel_futures=False):
732
747
self ._result_queue .close ()
733
748
self ._result_queue = None
734
749
self ._processes = None
735
-
736
- if self ._executor_manager_thread_wakeup :
737
- self ._executor_manager_thread_wakeup = None
750
+ self ._executor_manager_thread_wakeup = None
738
751
739
752
shutdown .__doc__ = _base .Executor .shutdown .__doc__
0 commit comments