summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrei Bodrov <Drino@users.noreply.github.com>2024-11-22 16:20:34 (GMT)
committerGitHub <noreply@github.com>2024-11-22 16:20:34 (GMT)
commit1848ce61f349533ae5892a8c24c2e0e3c364fc8a (patch)
tree47491803ee64856ca9c662d0d8f306eade05c0dd
parent7725c0371a93be3ccfaff4871014a80bdf0ea274 (diff)
downloadcpython-1848ce61f349533ae5892a8c24c2e0e3c364fc8a.zip
cpython-1848ce61f349533ae5892a8c24c2e0e3c364fc8a.tar.gz
cpython-1848ce61f349533ae5892a8c24c2e0e3c364fc8a.tar.bz2
gh-88110: Clear concurrent.futures.thread._threads_queues after fork to avoid joining parent process' threads (GH-126098)
Threads are gone after fork, so clear the queues too. Otherwise the child process (here created via multiprocessing.Process) crashes on interpreter exit. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
-rw-r--r--Lib/concurrent/futures/thread.py1
-rw-r--r--Lib/test/test_concurrent_futures/test_thread_pool.py19
-rw-r--r--Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst2
3 files changed, 22 insertions, 0 deletions
diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py
index 16cc553..909359b 100644
--- a/Lib/concurrent/futures/thread.py
+++ b/Lib/concurrent/futures/thread.py
@@ -41,6 +41,7 @@ if hasattr(os, 'register_at_fork'):
os.register_at_fork(before=_global_shutdown_lock.acquire,
after_in_child=_global_shutdown_lock._at_fork_reinit,
after_in_parent=_global_shutdown_lock.release)
+ os.register_at_fork(after_in_child=_threads_queues.clear)
class WorkerContext:
diff --git a/Lib/test/test_concurrent_futures/test_thread_pool.py b/Lib/test/test_concurrent_futures/test_thread_pool.py
index 2b5bea9..4324241 100644
--- a/Lib/test/test_concurrent_futures/test_thread_pool.py
+++ b/Lib/test/test_concurrent_futures/test_thread_pool.py
@@ -66,6 +66,25 @@ class ThreadPoolExecutorTest(ThreadPoolMixin, ExecutorTest, BaseTestCase):
with futures.ProcessPoolExecutor(1, mp_context=mp.get_context('fork')) as workers:
workers.submit(tuple)
+ @support.requires_fork()
+ @unittest.skipUnless(hasattr(os, 'register_at_fork'), 'need os.register_at_fork')
+ def test_process_fork_from_a_threadpool(self):
+ # bpo-43944: clear concurrent.futures.thread._threads_queues after fork,
+ # otherwise child process will try to join parent thread
+ def fork_process_and_return_exitcode():
+ # Ignore the warning about fork with threads.
+ with self.assertWarnsRegex(DeprecationWarning,
+ r"use of fork\(\) may lead to deadlocks in the child"):
+ p = mp.get_context('fork').Process(target=lambda: 1)
+ p.start()
+ p.join()
+ return p.exitcode
+
+ with futures.ThreadPoolExecutor(1) as pool:
+ process_exitcode = pool.submit(fork_process_and_return_exitcode).result()
+
+ self.assertEqual(process_exitcode, 0)
+
def test_executor_map_current_future_cancel(self):
stop_event = threading.Event()
log = []
diff --git a/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst b/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst
new file mode 100644
index 0000000..42a83ed
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-02-15-23-54-42.gh-issue-88110.KU6erv.rst
@@ -0,0 +1,2 @@
+Fixed :class:`multiprocessing.Process` reporting a ``.exitcode`` of 1 even on success when
+using the ``"fork"`` start method while using a :class:`concurrent.futures.ThreadPoolExecutor`.