bpo-39812: Remove daemon threads in concurrent.futures (GH-19149)
Remove daemon threads from :mod:`concurrent.futures` by adding
an internal `threading._register_atexit()`, which calls registered functions
prior to joining all non-daemon threads. This allows for compatibility
with subinterpreters, which don't support daemon threads.
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index f1037b5..da17e12 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -1397,5 +1397,55 @@
signal.signal(signal.SIGINT, handler)
+class AtexitTests(unittest.TestCase):
+
+ def test_atexit_output(self):
+ rc, out, err = assert_python_ok("-c", """if True:
+ import threading
+
+ def run_last():
+ print('parrot')
+
+ threading._register_atexit(run_last)
+ """)
+
+ self.assertFalse(err)
+ self.assertEqual(out.strip(), b'parrot')
+
+ def test_atexit_called_once(self):
+ rc, out, err = assert_python_ok("-c", """if True:
+ import threading
+ from unittest.mock import Mock
+
+ mock = Mock()
+ threading._register_atexit(mock)
+ mock.assert_not_called()
+ # force early shutdown to ensure it was called once
+ threading._shutdown()
+ mock.assert_called_once()
+ """)
+
+ self.assertFalse(err)
+
+ def test_atexit_after_shutdown(self):
+ # The only way to do this is by registering an atexit within
+ # an atexit, which is intended to raise an exception.
+ rc, out, err = assert_python_ok("-c", """if True:
+ import threading
+
+ def func():
+ pass
+
+ def run_last():
+ threading._register_atexit(func)
+
+ threading._register_atexit(run_last)
+ """)
+
+ self.assertTrue(err)
+ self.assertIn("RuntimeError: can't register atexit after shutdown",
+ err.decode())
+
+
if __name__ == "__main__":
unittest.main()