Skip to content

Commit cf70854

Browse files
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 5909a49) Co-authored-by: Victor Stinner <[email protected]>
1 parent 7c4d8fa commit cf70854

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
@@ -439,6 +439,35 @@ def test_daemon_param(self):
439439
t = threading.Thread(daemon=True)
440440
self.assertTrue(t.daemon)
441441

442+
@unittest.skipUnless(hasattr(os, 'fork'), 'needs os.fork()')
443+
def test_fork_at_exit(self):
444+
# bpo-42350: Calling os.fork() after threading._shutdown() must
445+
# not log an error.
446+
code = textwrap.dedent("""
447+
import atexit
448+
import os
449+
import sys
450+
from test.support import wait_process
451+
452+
# Import the threading module to register its "at fork" callback
453+
import threading
454+
455+
def exit_handler():
456+
pid = os.fork()
457+
if not pid:
458+
print("child process ok", file=sys.stderr, flush=True)
459+
# child process
460+
sys.exit()
461+
else:
462+
wait_process(pid, exitcode=0)
463+
464+
# exit_handler() will be called after threading._shutdown()
465+
atexit.register(exit_handler)
466+
""")
467+
_, out, err = assert_python_ok("-c", code)
468+
self.assertEqual(out, b'')
469+
self.assertEqual(err.rstrip(), b'child process ok')
470+
442471
@unittest.skipUnless(hasattr(os, 'fork'), 'test needs fork()')
443472
def test_dummy_thread_after_fork(self):
444473
# 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
@@ -826,8 +826,12 @@ def _reset_internal_locks(self, is_alive):
826826
# they may be in an invalid state leading to a deadlock or crash.
827827
self._started._at_fork_reinit()
828828
if is_alive:
829-
self._tstate_lock._at_fork_reinit()
830-
self._tstate_lock.acquire()
829+
# bpo-42350: If the fork happens when the thread is already stopped
830+
# (ex: after threading._shutdown() has been called), _tstate_lock
831+
# is None. Do nothing in this case.
832+
if self._tstate_lock is not None:
833+
self._tstate_lock._at_fork_reinit()
834+
self._tstate_lock.acquire()
831835
else:
832836
# The thread isn't alive after fork: it doesn't have a tstate
833837
# 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)