68
68
class _ThreadWakeup :
69
69
def __init__ (self ):
70
70
self ._closed = False
71
+ self ._lock = threading .Lock ()
71
72
self ._reader , self ._writer = mp .Pipe (duplex = False )
72
73
73
74
def close (self ):
74
- # Please note that we do not take the shutdown lock when
75
+ # Please note that we do not take the self._lock when
75
76
# calling clear() (to avoid deadlocking) so this method can
76
77
# only be called safely from the same thread as all calls to
77
- # clear() even if you hold the shutdown lock. Otherwise we
78
+ # clear() even if you hold the lock. Otherwise we
78
79
# might try to read from the closed pipe.
79
- if not self ._closed :
80
- self ._closed = True
81
- self ._writer .close ()
82
- self ._reader .close ()
80
+ with self ._lock :
81
+ if not self ._closed :
82
+ self ._closed = True
83
+ self ._writer .close ()
84
+ self ._reader .close ()
83
85
84
86
def wakeup (self ):
85
- if not self ._closed :
86
- self ._writer .send_bytes (b"" )
87
+ with self ._lock :
88
+ if not self ._closed :
89
+ self ._writer .send_bytes (b"" )
87
90
88
91
def clear (self ):
89
- if not self ._closed :
90
- while self ._reader .poll ():
91
- self ._reader .recv_bytes ()
92
+ if self ._closed :
93
+ raise RuntimeError ('operation on closed _ThreadWakeup' )
94
+ while self ._reader .poll ():
95
+ self ._reader .recv_bytes ()
92
96
93
97
94
98
def _python_exit ():
@@ -167,10 +171,8 @@ def __init__(self, work_id, fn, args, kwargs):
167
171
168
172
class _SafeQueue (Queue ):
169
173
"""Safe Queue set exception to the future object linked to a job"""
170
- def __init__ (self , max_size = 0 , * , ctx , pending_work_items , shutdown_lock ,
171
- thread_wakeup ):
174
+ def __init__ (self , max_size = 0 , * , ctx , pending_work_items , thread_wakeup ):
172
175
self .pending_work_items = pending_work_items
173
- self .shutdown_lock = shutdown_lock
174
176
self .thread_wakeup = thread_wakeup
175
177
super ().__init__ (max_size , ctx = ctx )
176
178
@@ -179,8 +181,7 @@ def _on_queue_feeder_error(self, e, obj):
179
181
tb = format_exception (type (e ), e , e .__traceback__ )
180
182
e .__cause__ = _RemoteTraceback ('\n """\n {}"""' .format ('' .join (tb )))
181
183
work_item = self .pending_work_items .pop (obj .work_id , None )
182
- with self .shutdown_lock :
183
- self .thread_wakeup .wakeup ()
184
+ self .thread_wakeup .wakeup ()
184
185
# work_item can be None if another process terminated. In this
185
186
# case, the executor_manager_thread fails all work_items
186
187
# with BrokenProcessPool
@@ -296,12 +297,10 @@ def __init__(self, executor):
296
297
# if there is no pending work item.
297
298
def weakref_cb (_ ,
298
299
thread_wakeup = self .thread_wakeup ,
299
- shutdown_lock = self .shutdown_lock ,
300
300
mp_util_debug = mp .util .debug ):
301
301
mp_util_debug ('Executor collected: triggering callback for'
302
302
' QueueManager wakeup' )
303
- with shutdown_lock :
304
- thread_wakeup .wakeup ()
303
+ thread_wakeup .wakeup ()
305
304
306
305
self .executor_reference = weakref .ref (executor , weakref_cb )
307
306
@@ -429,11 +428,6 @@ def wait_result_broken_or_wakeup(self):
429
428
elif wakeup_reader in ready :
430
429
is_broken = False
431
430
432
- # No need to hold the _shutdown_lock here because:
433
- # 1. we're the only thread to use the wakeup reader
434
- # 2. we're also the only thread to call thread_wakeup.close()
435
- # 3. we want to avoid a possible deadlock when both reader and writer
436
- # would block (gh-105829)
437
431
self .thread_wakeup .clear ()
438
432
439
433
return result_item , is_broken , cause
@@ -721,10 +715,9 @@ def __init__(self, max_workers=None, mp_context=None,
721
715
# as it could result in a deadlock if a worker process dies with the
722
716
# _result_queue write lock still acquired.
723
717
#
724
- # _shutdown_lock must be locked to access _ThreadWakeup.close() and
725
- # .wakeup(). Care must also be taken to not call clear or close from
726
- # more than one thread since _ThreadWakeup.clear() is not protected by
727
- # the _shutdown_lock
718
+ # Care must be taken to only call clear and close from the
719
+ # executor_manager_thread, since _ThreadWakeup.clear() is not protected
720
+ # by a lock.
728
721
self ._executor_manager_thread_wakeup = _ThreadWakeup ()
729
722
730
723
# Create communication channels for the executor
@@ -735,7 +728,6 @@ def __init__(self, max_workers=None, mp_context=None,
735
728
self ._call_queue = _SafeQueue (
736
729
max_size = queue_size , ctx = self ._mp_context ,
737
730
pending_work_items = self ._pending_work_items ,
738
- shutdown_lock = self ._shutdown_lock ,
739
731
thread_wakeup = self ._executor_manager_thread_wakeup )
740
732
# Killed worker processes can produce spurious "broken pipe"
741
733
# tracebacks in the queue's own worker thread. But we detect killed
0 commit comments