From 59ace509f3a67a11ab5482b0edd57ef760d9bfb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 7 Mar 2023 23:53:40 +0100 Subject: [PATCH 1/3] Add regression test for gh-102512 --- Lib/test/test_threading.py | 49 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index a39a267b403d83..a40de2f9daeefa 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -683,6 +683,55 @@ def func(): self.assertEqual(err.decode('utf-8'), "") self.assertEqual(data, "Thread-1 (func)\nTrue\nTrue\n") + @unittest.skipIf(sys.platform in platforms_to_skip, "due to known OS bug") + @support.requires_fork() + @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()") + def test_main_thread_after_fork_from_foreign_thread(self): + code = """if 1: + import os, threading, sys, traceback, _thread + from test import support + + def func(lock): + # call current_thread() before fork to allocate DummyThread + current = threading.current_thread() + print(current.name) + # flush before fork, so child won't flush it again + sys.stdout.flush() + pid = os.fork() + if pid == 0: + main = threading.main_thread() + print(main.ident == threading.current_thread().ident) + print(main.ident == threading.get_ident()) + # stdout is fully buffered because not a tty, + # we have to flush before exit. + sys.stdout.flush() + try: + threading._shutdown() + os._exit(0) + except: + os._exit(1) + else: + try: + support.wait_process(pid, exitcode=0) + except Exception as e: + # avoid 'could not acquire lock for + # <_io.BufferedWriter name=''> at interpreter shutdown,' + traceback.print_exc() + sys.stderr.flush() + finally: + lock.release() + + join_lock = _thread.allocate_lock() + join_lock.acquire() + th = _thread.start_new_thread(func, (join_lock,)) + join_lock.acquire() + """ + # "DeprecationWarning: This process is multi-threaded, use of fork() may lead to deadlocks in the child" + _, out, err = assert_python_ok("-W", "ignore::DeprecationWarning", "-c", code) + data = out.decode().replace('\r', '') + self.assertEqual(err, b"") + self.assertEqual(data, "Dummy-1\nTrue\nTrue\n") + def test_main_thread_during_shutdown(self): # bpo-31516: current_thread() should still point to the main thread # at shutdown From 43c9f3b2c85fadd038e4ceaef8859154512fdfcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 7 Mar 2023 23:56:18 +0100 Subject: [PATCH 2/3] Initialize tstate_lock in DummyThread too If os.fork() is called from a DummyThread (a thread started not by the threading module, in which somebody called threading.current_thread()), it becomes main thread. Threading shutdown logic relies on the main thread having tstate_lock, so initialize it for the DummyThread too. Fixes #102512 --- Lib/threading.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/threading.py b/Lib/threading.py index df273870fa4273..99f87c70353bd5 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1439,7 +1439,7 @@ class _DummyThread(Thread): def __init__(self): Thread.__init__(self, name=_newname("Dummy-%d"), daemon=_daemon_threads_allowed()) - + self._set_tstate_lock() self._started.set() self._set_ident() if _HAVE_THREAD_NATIVE_ID: From 9d186b3ed544e0f43f424e8bf511994b2256ce9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 8 Mar 2023 00:02:34 +0100 Subject: [PATCH 3/3] Add NEWS entry --- .../next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst diff --git a/Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst b/Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst new file mode 100644 index 00000000000000..81f95a06ffb941 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-03-08-00-02-30.gh-issue-102512.LiugDr.rst @@ -0,0 +1,2 @@ +Fix handling threading after os.fork() called from a foreign thread (aka +DummyThread).