diff options
author | Serhiy Storchaka <storchaka@gmail.com> | 2024-01-22 15:00:33 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-22 15:00:33 (GMT) |
commit | a0f30b04fe3a54ffe4dbd39bd927fc45a0896f2d (patch) | |
tree | e7ccf229147b50dcecdd9ddca0539477b89e56f2 /Lib | |
parent | 459726f6cde7867ee33a31f9fab250dc9d421367 (diff) | |
download | cpython-a0f30b04fe3a54ffe4dbd39bd927fc45a0896f2d.zip cpython-a0f30b04fe3a54ffe4dbd39bd927fc45a0896f2d.tar.gz cpython-a0f30b04fe3a54ffe4dbd39bd927fc45a0896f2d.tar.bz2 |
[3.11] gh-102512: Turn _DummyThread into _MainThread after os.fork() called from a foreign thread (GH-113261) (GH-114431)
Always set a _MainThread as a main thread after os.fork() is called from
a thread started not by the threading module.
A new _MainThread was already set as a new main thread after fork if
threading.current_thread() was not called for a foreign thread before fork.
Now, if it was called before fork, the implicitly created _DummyThread will
be turned into _MainThread after fork.
It fixes, in particularly, an incompatibility of _DummyThread with
the threading shutdown logic which relies on the main thread
having tstate_lock.
(cherry picked from commit 49785b06ded19c7c4afce186bac90fea707470ea)
Co-authored-by: Marek Marczykowski-Górecki <marmarek@invisiblethingslab.com>
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_threading.py | 102 | ||||
-rw-r--r-- | Lib/threading.py | 5 |
2 files changed, 96 insertions, 11 deletions
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 17319c3..ec6a319 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -106,6 +106,7 @@ class BaseTestCase(unittest.TestCase): class ThreadTests(BaseTestCase): + maxDiff = 9999 @cpython_only def test_name(self): @@ -639,19 +640,25 @@ class ThreadTests(BaseTestCase): import os, threading from test import support + ident = threading.get_ident() pid = os.fork() if pid == 0: + print("current ident", threading.get_ident() == ident) main = threading.main_thread() - print(main.name) - print(main.ident == threading.current_thread().ident) - print(main.ident == threading.get_ident()) + print("main", main.name) + print("main ident", main.ident == ident) + print("current is main", threading.current_thread() is main) else: support.wait_process(pid, exitcode=0) """ _, out, err = assert_python_ok("-c", code) data = out.decode().replace('\r', '') self.assertEqual(err, b"") - self.assertEqual(data, "MainThread\nTrue\nTrue\n") + self.assertEqual(data, + "current ident True\n" + "main MainThread\n" + "main ident True\n" + "current is main True\n") @skip_unless_reliable_fork @unittest.skipUnless(hasattr(os, 'waitpid'), "test needs os.waitpid()") @@ -661,17 +668,17 @@ class ThreadTests(BaseTestCase): from test import support def func(): + ident = threading.get_ident() pid = os.fork() if pid == 0: + print("current ident", threading.get_ident() == ident) main = threading.main_thread() - print(main.name) - print(main.ident == threading.current_thread().ident) - print(main.ident == threading.get_ident()) + print("main", main.name, type(main).__name__) + print("main ident", main.ident == ident) + print("current is main", threading.current_thread() is main) # stdout is fully buffered because not a tty, # we have to flush before exit. sys.stdout.flush() - else: - support.wait_process(pid, exitcode=0) th = threading.Thread(target=func) th.start() @@ -679,8 +686,81 @@ class ThreadTests(BaseTestCase): """ _, out, err = assert_python_ok("-c", code) data = out.decode().replace('\r', '') - self.assertEqual(err, b"") - self.assertEqual(data, "Thread-1 (func)\nTrue\nTrue\n") + self.assertEqual(err.decode('utf-8'), "") + self.assertEqual(data, + "current ident True\n" + "main Thread-1 (func) Thread\n" + "main ident True\n" + "current is main True\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, create_dummy=False): + code = """if 1: + import os, threading, sys, traceback, _thread + from test import support + + def func(lock): + ident = threading.get_ident() + if %s: + # call current_thread() before fork to allocate DummyThread + current = threading.current_thread() + print("current", current.name, type(current).__name__) + print("ident in _active", ident in threading._active) + # flush before fork, so child won't flush it again + sys.stdout.flush() + pid = os.fork() + if pid == 0: + print("current ident", threading.get_ident() == ident) + main = threading.main_thread() + print("main", main.name, type(main).__name__) + print("main ident", main.ident == ident) + print("current is main", threading.current_thread() is main) + print("_dangling", [t.name for t in list(threading._dangling)]) + # stdout is fully buffered because not a tty, + # we have to flush before exit. + sys.stdout.flush() + try: + threading._shutdown() + os._exit(0) + except: + traceback.print_exc() + sys.stderr.flush() + os._exit(1) + else: + try: + support.wait_process(pid, exitcode=0) + except Exception: + # avoid 'could not acquire lock for + # <_io.BufferedWriter name='<stderr>'> 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() + """ % create_dummy + # "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.decode(), "") + self.assertEqual(data, + ("current Dummy-1 _DummyThread\n" if create_dummy else "") + + f"ident in _active {create_dummy!s}\n" + + "current ident True\n" + "main MainThread _MainThread\n" + "main ident True\n" + "current is main True\n" + "_dangling ['MainThread']\n") + + def test_main_thread_after_fork_from_dummy_thread(self, create_dummy=False): + self.test_main_thread_after_fork_from_foreign_thread(create_dummy=True) def test_main_thread_during_shutdown(self): # bpo-31516: current_thread() should still point to the main thread diff --git a/Lib/threading.py b/Lib/threading.py index 70601fc..29b8ec7 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1651,6 +1651,11 @@ def _after_fork(): # its new value since it can have changed. thread._reset_internal_locks(True) ident = get_ident() + if isinstance(thread, _DummyThread): + thread.__class__ = _MainThread + thread._name = 'MainThread' + thread._daemonic = False + thread._set_tstate_lock() thread._ident = ident new_active[ident] = thread else: |