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.
(cherry picked from commit 5909a494cd)

Co-authored-by: Victor Stinner <vstinner@python.org>
This commit is contained in:
Miss Islington (bot) 2020-11-16 07:17:17 -08:00 committed by GitHub
parent 7c4d8fa82a
commit cf70854f10
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 38 additions and 2 deletions

View File

@ -439,6 +439,35 @@ class ThreadTests(BaseTestCase):
t = threading.Thread(daemon=True) t = threading.Thread(daemon=True)
self.assertTrue(t.daemon) 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()') @unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()')
def test_dummy_thread_after_fork(self): def test_dummy_thread_after_fork(self):
# Issue #14308: a dummy thread in the active list doesn't mess up # Issue #14308: a dummy thread in the active list doesn't mess up

View File

@ -826,8 +826,12 @@ class Thread:
# they may be in an invalid state leading to a deadlock or crash. # they may be in an invalid state leading to a deadlock or crash.
self._started._at_fork_reinit() self._started._at_fork_reinit()
if is_alive: if is_alive:
self._tstate_lock._at_fork_reinit() # bpo-42350: If the fork happens when the thread is already stopped
self._tstate_lock.acquire() # (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: else:
# The thread isn't alive after fork: it doesn't have a tstate # The thread isn't alive after fork: it doesn't have a tstate
# anymore. # anymore.

View File

@ -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.