From 5909a494cd3ba43143b28bd439773ed85a485dfc Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 16 Nov 2020 15:20:34 +0100 Subject: bpo-42350: Fix Thread._reset_internal_locks() (GH-23268) Fix the threading.Thread class at fork: do nothing if the thread is already stopped (ex: fork called at Python exit). Previously, an error was logged in the child process. --- Lib/test/test_threading.py | 29 ++++++++++++++++++++++ Lib/threading.py | 8 ++++-- .../2020-11-13-18-53-50.bpo-42350.rsql7V.rst | 3 +++ 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2020-11-13-18-53-50.bpo-42350.rsql7V.rst diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index db440d4..864cea3 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -469,6 +469,35 @@ class ThreadTests(BaseTestCase): t = threading.Thread(daemon=True) self.assertTrue(t.daemon) + @unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()') + def test_fork_at_exit(self): + # bpo-42350: Calling os.fork() after threading._shutdown() must + # not log an error. + code = textwrap.dedent(""" + import atexit + import os + import sys + from test.support import wait_process + + # Import the threading module to register its "at fork" callback + import threading + + def exit_handler(): + pid = os.fork() + if not pid: + print("child process ok", file=sys.stderr, flush=True) + # child process + sys.exit() + else: + wait_process(pid, exitcode=0) + + # exit_handler() will be called after threading._shutdown() + atexit.register(exit_handler) + """) + _, out, err = assert_python_ok("-c", code) + self.assertEqual(out, b'') + self.assertEqual(err.rstrip(), b'child process ok') + @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()') def test_dummy_thread_after_fork(self): # Issue #14308: a dummy thread in the active list doesn't mess up diff --git a/Lib/threading.py b/Lib/threading.py index 7dae77d..7b3d63d 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -844,8 +844,12 @@ class Thread: # they may be in an invalid state leading to a deadlock or crash. self._started._at_fork_reinit() if is_alive: - self._tstate_lock._at_fork_reinit() - self._tstate_lock.acquire() + # bpo-42350: If the fork happens when the thread is already stopped + # (ex: after threading._shutdown() has been called), _tstate_lock + # is None. Do nothing in this case. + if self._tstate_lock is not None: + self._tstate_lock._at_fork_reinit() + self._tstate_lock.acquire() else: # The thread isn't alive after fork: it doesn't have a tstate # anymore. diff --git a/Misc/NEWS.d/next/Library/2020-11-13-18-53-50.bpo-42350.rsql7V.rst b/Misc/NEWS.d/next/Library/2020-11-13-18-53-50.bpo-42350.rsql7V.rst new file mode 100644 index 0000000..090ea22 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-11-13-18-53-50.bpo-42350.rsql7V.rst @@ -0,0 +1,3 @@ +Fix the :class:`threading.Thread` class at fork: do nothing if the thread is +already stopped (ex: fork called at Python exit). Previously, an error was +logged in the child process. -- cgit v0.12