Skip to content

Commit 5909a49

Browse files
authored
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.
1 parent 3df5c68 commit 5909a49

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

Lib/test/test_threading.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,35 @@ def test_daemon_param(self):
469469
t = threading.Thread(daemon=True)
470470
self.assertTrue(t.daemon)
471471

472+
@unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()')
473+
def test_fork_at_exit(self):
474+
# bpo-42350: Calling os.fork() after threading._shutdown() must
475+
# not log an error.
476+
code = textwrap.dedent("""
477+
import atexit
478+
import os
479+
import sys
480+
from test.support import wait_process
481+
482+
# Import the threading module to register its "at fork" callback
483+
import threading
484+
485+
def exit_handler():
486+
pid = os.fork()
487+
if not pid:
488+
print("child process ok", file=sys.stderr, flush=True)
489+
# child process
490+
sys.exit()
491+
else:
492+
wait_process(pid, exitcode=0)
493+
494+
# exit_handler() will be called after threading._shutdown()
495+
atexit.register(exit_handler)
496+
""")
497+
_, out, err = assert_python_ok("-c", code)
498+
self.assertEqual(out, b'')
499+
self.assertEqual(err.rstrip(), b'child process ok')
500+
472501
@unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()')
473502
def test_dummy_thread_after_fork(self):
474503
# Issue #14308: a dummy thread in the active list doesn't mess up

Lib/threading.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -844,8 +844,12 @@ def _reset_internal_locks(self, is_alive):
844844
# they may be in an invalid state leading to a deadlock or crash.
845845
self._started._at_fork_reinit()
846846
if is_alive:
847-
self._tstate_lock._at_fork_reinit()
848-
self._tstate_lock.acquire()
847+
# bpo-42350: If the fork happens when the thread is already stopped
848+
# (ex: after threading._shutdown() has been called), _tstate_lock
849+
# is None. Do nothing in this case.
850+
if self._tstate_lock is not None:
851+
self._tstate_lock._at_fork_reinit()
852+
self._tstate_lock.acquire()
849853
else:
850854
# The thread isn't alive after fork: it doesn't have a tstate
851855
# anymore.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix the :class:`threading.Thread` class at fork: do nothing if the thread is
2+
already stopped (ex: fork called at Python exit). Previously, an error was
3+
logged in the child process.

0 commit comments

Comments
 (0)