Skip to content

Commit e9ab197

Browse files
committed
fix(concurrent): resolve ThreadPoolExecutor shutdown deadlock in signal handlers
1 parent d63c994 commit e9ab197

1 file changed

Lines changed: 12 additions & 2 deletions

File tree

Lib/concurrent/futures/thread.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)