Skip to content

Commit b1133c8

Browse files
committed
bpo-33097: Fix submit accepting callable after executor shutdown by interpreter exit. (GH-6144)
Executors in concurrent.futures accepted tasks after executor was shutdown by interpreter exit. Tasks were left in PENDING state forever. This fix changes submit to instead raise a RuntimeError.
1 parent bf63e8d commit b1133c8

4 files changed

Lines changed: 31 additions & 2 deletions

File tree

Lib/concurrent/futures/process.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ def submit(self, fn, *args, **kwargs):
591591
with self._shutdown_lock:
592592
if self._broken:
593593
raise BrokenProcessPool(self._broken)
594-
if self._shutdown_thread:
594+
if _global_shutdown or self._shutdown_thread:
595595
raise RuntimeError('cannot schedule new futures after shutdown')
596596

597597
f = _base.Future()

Lib/concurrent/futures/thread.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def submit(self, fn, *args, **kwargs):
143143
if self._broken:
144144
raise BrokenThreadPool(self._broken)
145145

146-
if self._shutdown:
146+
if _shutdown or self._shutdown:
147147
raise RuntimeError('cannot schedule new futures after shutdown')
148148

149149
f = _base.Future()

Lib/test/test_concurrent_futures.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,33 @@ def test_interpreter_shutdown(self):
303303
self.assertFalse(err)
304304
self.assertEqual(out.strip(), b"apple")
305305

306+
def test_submit_after_interpreter_shutdown(self):
307+
# Test the atexit hook for shutdown of worker threads and processes
308+
rc, out, err = assert_python_ok('-c', """if 1:
309+
import atexit
310+
@atexit.register
311+
def run_last():
312+
try:
313+
t.submit(lambda: None)
314+
except RuntimeError:
315+
print("runtime-error")
316+
raise
317+
from concurrent.futures import {executor_type}
318+
if __name__ == "__main__":
319+
context = '{context}'
320+
if context == "":
321+
t = {executor_type}(5)
322+
else:
323+
from multiprocessing import get_context
324+
context = get_context(context)
325+
t = {executor_type}(5, mp_context=context)
326+
""".format(executor_type=self.executor_type.__name__,
327+
context=getattr(self, "ctx", "")))
328+
# Errors in atexit hooks don't change the process exit code, check
329+
# stderr manually.
330+
self.assertTrue(err)
331+
self.assertEqual(out.strip(), b"runtime-error")
332+
306333
def test_hang_issue12364(self):
307334
fs = [self.executor.submit(time.sleep, 0.1) for _ in range(50)]
308335
self.executor.shutdown()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Raise RuntimeError when ``executor.submit`` is called during interpreter
2+
shutdown.

0 commit comments

Comments
 (0)