Skip to content

Commit 60e105c

Browse files
authored
gh-113964: Don't prevent new threads until all non-daemon threads exit (#116677)
Starting in Python 3.12, we prevented calling fork() and starting new threads during interpreter finalization (shutdown). This has led to a number of regressions and flaky tests. We should not prevent starting new threads (or `fork()`) until all non-daemon threads exit and finalization starts in earnest. This changes the checks to use `_PyInterpreterState_GetFinalizing(interp)`, which is set immediately before terminating non-daemon threads.
1 parent 025ef7a commit 60e105c

File tree

8 files changed

+57
-27
lines changed

8 files changed

+57
-27
lines changed

Lib/test/test_os.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -5357,20 +5357,21 @@ def test_fork_warns_when_non_python_thread_exists(self):
53575357
self.assertEqual(err.decode("utf-8"), "")
53585358
self.assertEqual(out.decode("utf-8"), "")
53595359

5360-
def test_fork_at_exit(self):
5360+
def test_fork_at_finalization(self):
53615361
code = """if 1:
53625362
import atexit
53635363
import os
53645364
5365-
def exit_handler():
5366-
pid = os.fork()
5367-
if pid != 0:
5368-
print("shouldn't be printed")
5369-
5370-
atexit.register(exit_handler)
5365+
class AtFinalization:
5366+
def __del__(self):
5367+
print("OK")
5368+
pid = os.fork()
5369+
if pid != 0:
5370+
print("shouldn't be printed")
5371+
at_finalization = AtFinalization()
53715372
"""
53725373
_, out, err = assert_python_ok("-c", code)
5373-
self.assertEqual(b"", out)
5374+
self.assertEqual(b"OK\n", out)
53745375
self.assertIn(b"can't fork at interpreter shutdown", err)
53755376

53765377

Lib/test/test_subprocess.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -3398,14 +3398,15 @@ def test_preexec_at_exit(self):
33983398
def dummy():
33993399
pass
34003400
3401-
def exit_handler():
3402-
subprocess.Popen({ZERO_RETURN_CMD}, preexec_fn=dummy)
3403-
print("shouldn't be printed")
3404-
3405-
atexit.register(exit_handler)
3401+
class AtFinalization:
3402+
def __del__(self):
3403+
print("OK")
3404+
subprocess.Popen({ZERO_RETURN_CMD}, preexec_fn=dummy)
3405+
print("shouldn't be printed")
3406+
at_finalization = AtFinalization()
34063407
"""
34073408
_, out, err = assert_python_ok("-c", code)
3408-
self.assertEqual(out, b'')
3409+
self.assertEqual(out.strip(), b"OK")
34093410
self.assertIn(b"preexec_fn not supported at interpreter shutdown", err)
34103411

34113412
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),

Lib/test/test_threading.py

+31-7
Original file line numberDiff line numberDiff line change
@@ -1154,21 +1154,21 @@ def import_threading():
11541154
self.assertEqual(out, b'')
11551155
self.assertEqual(err, b'')
11561156

1157-
def test_start_new_thread_at_exit(self):
1157+
def test_start_new_thread_at_finalization(self):
11581158
code = """if 1:
1159-
import atexit
11601159
import _thread
11611160
11621161
def f():
11631162
print("shouldn't be printed")
11641163
1165-
def exit_handler():
1166-
_thread.start_new_thread(f, ())
1167-
1168-
atexit.register(exit_handler)
1164+
class AtFinalization:
1165+
def __del__(self):
1166+
print("OK")
1167+
_thread.start_new_thread(f, ())
1168+
at_finalization = AtFinalization()
11691169
"""
11701170
_, out, err = assert_python_ok("-c", code)
1171-
self.assertEqual(out, b'')
1171+
self.assertEqual(out.strip(), b"OK")
11721172
self.assertIn(b"can't create new thread at interpreter shutdown", err)
11731173

11741174
class ThreadJoinOnShutdown(BaseTestCase):
@@ -1297,6 +1297,30 @@ def main():
12971297
rc, out, err = assert_python_ok('-c', script)
12981298
self.assertFalse(err)
12991299

1300+
def test_thread_from_thread(self):
1301+
script = """if True:
1302+
import threading
1303+
import time
1304+
1305+
def thread2():
1306+
time.sleep(0.05)
1307+
print("OK")
1308+
1309+
def thread1():
1310+
time.sleep(0.05)
1311+
t2 = threading.Thread(target=thread2)
1312+
t2.start()
1313+
1314+
t = threading.Thread(target=thread1)
1315+
t.start()
1316+
# do not join() -- the interpreter waits for non-daemon threads to
1317+
# finish.
1318+
"""
1319+
rc, out, err = assert_python_ok('-c', script)
1320+
self.assertEqual(err, b"")
1321+
self.assertEqual(out.strip(), b"OK")
1322+
self.assertEqual(rc, 0)
1323+
13001324
@skip_unless_reliable_fork
13011325
def test_reinit_tls_after_fork(self):
13021326
# Issue #13817: fork() would deadlock in a multithreaded program with
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Starting new threads and process creation through :func:`os.fork` are now
2+
only prevented once all non-daemon threads exit.

Modules/_posixsubprocess.c

+3-1
Original file line numberDiff line numberDiff line change
@@ -1031,7 +1031,9 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
10311031
Py_ssize_t fds_to_keep_len = PyTuple_GET_SIZE(py_fds_to_keep);
10321032

10331033
PyInterpreterState *interp = _PyInterpreterState_GET();
1034-
if ((preexec_fn != Py_None) && interp->finalizing) {
1034+
if ((preexec_fn != Py_None) &&
1035+
_PyInterpreterState_GetFinalizing(interp) != NULL)
1036+
{
10351037
PyErr_SetString(PyExc_PythonFinalizationError,
10361038
"preexec_fn not supported at interpreter shutdown");
10371039
return NULL;

Modules/_threadmodule.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -1729,7 +1729,7 @@ do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args,
17291729
"thread is not supported for isolated subinterpreters");
17301730
return -1;
17311731
}
1732-
if (interp->finalizing) {
1732+
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
17331733
PyErr_SetString(PyExc_PythonFinalizationError,
17341734
"can't create new thread at interpreter shutdown");
17351735
return -1;

Modules/posixmodule.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -7842,7 +7842,7 @@ os_fork1_impl(PyObject *module)
78427842
pid_t pid;
78437843

78447844
PyInterpreterState *interp = _PyInterpreterState_GET();
7845-
if (interp->finalizing) {
7845+
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
78467846
PyErr_SetString(PyExc_PythonFinalizationError,
78477847
"can't fork at interpreter shutdown");
78487848
return NULL;
@@ -7886,7 +7886,7 @@ os_fork_impl(PyObject *module)
78867886
{
78877887
pid_t pid;
78887888
PyInterpreterState *interp = _PyInterpreterState_GET();
7889-
if (interp->finalizing) {
7889+
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
78907890
PyErr_SetString(PyExc_PythonFinalizationError,
78917891
"can't fork at interpreter shutdown");
78927892
return NULL;
@@ -8719,7 +8719,7 @@ os_forkpty_impl(PyObject *module)
87198719
pid_t pid;
87208720

87218721
PyInterpreterState *interp = _PyInterpreterState_GET();
8722-
if (interp->finalizing) {
8722+
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
87238723
PyErr_SetString(PyExc_PythonFinalizationError,
87248724
"can't fork at interpreter shutdown");
87258725
return NULL;

Objects/unicodeobject.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ unicode_check_encoding_errors(const char *encoding, const char *errors)
505505

506506
/* Disable checks during Python finalization. For example, it allows to
507507
call _PyObject_Dump() during finalization for debugging purpose. */
508-
if (interp->finalizing) {
508+
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
509509
return 0;
510510
}
511511

0 commit comments

Comments
 (0)