@@ -192,7 +192,7 @@ def __init__(self, max_workers=None, thread_name_prefix='',
192192 self ._threads = set ()
193193 self ._broken = False
194194 self ._shutdown = False
195- self ._shutdown_lock = threading .Lock ()
195+ self ._shutdown_lock = threading .RLock ()
196196 self ._thread_name_prefix = (thread_name_prefix or
197197 ("ThreadPoolExecutor-%d" % self ._counter ()))
198198
@@ -212,6 +212,9 @@ def submit(self, fn, /, *args, **kwargs):
212212 w = _WorkItem (f , task )
213213
214214 self ._work_queue .put (w )
215+ if self ._shutdown or _shutdown :
216+ f .cancel ()
217+ raise RuntimeError ('cannot schedule new futures after shutdown' )
215218 self ._adjust_thread_count ()
216219 return f
217220 submit .__doc__ = _base .Executor .submit .__doc__
@@ -252,6 +255,10 @@ def _initializer_failed(self):
252255 work_item .future .set_exception (self .BROKEN (self ._broken ))
253256
254257 def shutdown (self , wait = True , * , cancel_futures = False ):
258+ # Detect if we are called reentrantly (e.g. from a signal handler on a thread
259+ # already holding self._shutdown_lock)
260+ reentrant = self ._shutdown_lock ._is_owned ()
261+
255262 with self ._shutdown_lock :
256263 self ._shutdown = True
257264 if cancel_futures :
@@ -268,7 +275,10 @@ def shutdown(self, wait=True, *, cancel_futures=False):
268275 # Send a wake-up to prevent threads calling
269276 # _work_queue.get(block=True) from permanently blocking.
270277 self ._work_queue .put (None )
271- if wait :
278+
279+ # If we are reentrant, we cannot join threads synchronously because the current
280+ # thread is interrupted and blocking it would cause a deadlock.
281+ if wait and not reentrant :
272282 for t in self ._threads :
273283 t .join ()
274284 shutdown .__doc__ = _base .Executor .shutdown .__doc__
0 commit comments