mirror of https://github.com/python/cpython
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.
This commit is contained in:
parent
025ef7a5f7
commit
60e105c1c1
|
@ -5357,20 +5357,21 @@ class ForkTests(unittest.TestCase):
|
||||||
self.assertEqual(err.decode("utf-8"), "")
|
self.assertEqual(err.decode("utf-8"), "")
|
||||||
self.assertEqual(out.decode("utf-8"), "")
|
self.assertEqual(out.decode("utf-8"), "")
|
||||||
|
|
||||||
def test_fork_at_exit(self):
|
def test_fork_at_finalization(self):
|
||||||
code = """if 1:
|
code = """if 1:
|
||||||
import atexit
|
import atexit
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def exit_handler():
|
class AtFinalization:
|
||||||
pid = os.fork()
|
def __del__(self):
|
||||||
if pid != 0:
|
print("OK")
|
||||||
print("shouldn't be printed")
|
pid = os.fork()
|
||||||
|
if pid != 0:
|
||||||
atexit.register(exit_handler)
|
print("shouldn't be printed")
|
||||||
|
at_finalization = AtFinalization()
|
||||||
"""
|
"""
|
||||||
_, out, err = assert_python_ok("-c", code)
|
_, out, err = assert_python_ok("-c", code)
|
||||||
self.assertEqual(b"", out)
|
self.assertEqual(b"OK\n", out)
|
||||||
self.assertIn(b"can't fork at interpreter shutdown", err)
|
self.assertIn(b"can't fork at interpreter shutdown", err)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3398,14 +3398,15 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||||
def dummy():
|
def dummy():
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def exit_handler():
|
class AtFinalization:
|
||||||
subprocess.Popen({ZERO_RETURN_CMD}, preexec_fn=dummy)
|
def __del__(self):
|
||||||
print("shouldn't be printed")
|
print("OK")
|
||||||
|
subprocess.Popen({ZERO_RETURN_CMD}, preexec_fn=dummy)
|
||||||
atexit.register(exit_handler)
|
print("shouldn't be printed")
|
||||||
|
at_finalization = AtFinalization()
|
||||||
"""
|
"""
|
||||||
_, out, err = assert_python_ok("-c", code)
|
_, out, err = assert_python_ok("-c", code)
|
||||||
self.assertEqual(out, b'')
|
self.assertEqual(out.strip(), b"OK")
|
||||||
self.assertIn(b"preexec_fn not supported at interpreter shutdown", err)
|
self.assertIn(b"preexec_fn not supported at interpreter shutdown", err)
|
||||||
|
|
||||||
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
|
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
|
||||||
|
|
|
@ -1154,21 +1154,21 @@ class ThreadTests(BaseTestCase):
|
||||||
self.assertEqual(out, b'')
|
self.assertEqual(out, b'')
|
||||||
self.assertEqual(err, b'')
|
self.assertEqual(err, b'')
|
||||||
|
|
||||||
def test_start_new_thread_at_exit(self):
|
def test_start_new_thread_at_finalization(self):
|
||||||
code = """if 1:
|
code = """if 1:
|
||||||
import atexit
|
|
||||||
import _thread
|
import _thread
|
||||||
|
|
||||||
def f():
|
def f():
|
||||||
print("shouldn't be printed")
|
print("shouldn't be printed")
|
||||||
|
|
||||||
def exit_handler():
|
class AtFinalization:
|
||||||
_thread.start_new_thread(f, ())
|
def __del__(self):
|
||||||
|
print("OK")
|
||||||
atexit.register(exit_handler)
|
_thread.start_new_thread(f, ())
|
||||||
|
at_finalization = AtFinalization()
|
||||||
"""
|
"""
|
||||||
_, out, err = assert_python_ok("-c", code)
|
_, out, err = assert_python_ok("-c", code)
|
||||||
self.assertEqual(out, b'')
|
self.assertEqual(out.strip(), b"OK")
|
||||||
self.assertIn(b"can't create new thread at interpreter shutdown", err)
|
self.assertIn(b"can't create new thread at interpreter shutdown", err)
|
||||||
|
|
||||||
class ThreadJoinOnShutdown(BaseTestCase):
|
class ThreadJoinOnShutdown(BaseTestCase):
|
||||||
|
@ -1297,6 +1297,30 @@ class ThreadJoinOnShutdown(BaseTestCase):
|
||||||
rc, out, err = assert_python_ok('-c', script)
|
rc, out, err = assert_python_ok('-c', script)
|
||||||
self.assertFalse(err)
|
self.assertFalse(err)
|
||||||
|
|
||||||
|
def test_thread_from_thread(self):
|
||||||
|
script = """if True:
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
def thread2():
|
||||||
|
time.sleep(0.05)
|
||||||
|
print("OK")
|
||||||
|
|
||||||
|
def thread1():
|
||||||
|
time.sleep(0.05)
|
||||||
|
t2 = threading.Thread(target=thread2)
|
||||||
|
t2.start()
|
||||||
|
|
||||||
|
t = threading.Thread(target=thread1)
|
||||||
|
t.start()
|
||||||
|
# do not join() -- the interpreter waits for non-daemon threads to
|
||||||
|
# finish.
|
||||||
|
"""
|
||||||
|
rc, out, err = assert_python_ok('-c', script)
|
||||||
|
self.assertEqual(err, b"")
|
||||||
|
self.assertEqual(out.strip(), b"OK")
|
||||||
|
self.assertEqual(rc, 0)
|
||||||
|
|
||||||
@skip_unless_reliable_fork
|
@skip_unless_reliable_fork
|
||||||
def test_reinit_tls_after_fork(self):
|
def test_reinit_tls_after_fork(self):
|
||||||
# Issue #13817: fork() would deadlock in a multithreaded program with
|
# Issue #13817: fork() would deadlock in a multithreaded program with
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Starting new threads and process creation through :func:`os.fork` are now
|
||||||
|
only prevented once all non-daemon threads exit.
|
|
@ -1031,7 +1031,9 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
|
||||||
Py_ssize_t fds_to_keep_len = PyTuple_GET_SIZE(py_fds_to_keep);
|
Py_ssize_t fds_to_keep_len = PyTuple_GET_SIZE(py_fds_to_keep);
|
||||||
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
if ((preexec_fn != Py_None) && interp->finalizing) {
|
if ((preexec_fn != Py_None) &&
|
||||||
|
_PyInterpreterState_GetFinalizing(interp) != NULL)
|
||||||
|
{
|
||||||
PyErr_SetString(PyExc_PythonFinalizationError,
|
PyErr_SetString(PyExc_PythonFinalizationError,
|
||||||
"preexec_fn not supported at interpreter shutdown");
|
"preexec_fn not supported at interpreter shutdown");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -1729,7 +1729,7 @@ do_start_new_thread(thread_module_state *state, PyObject *func, PyObject *args,
|
||||||
"thread is not supported for isolated subinterpreters");
|
"thread is not supported for isolated subinterpreters");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (interp->finalizing) {
|
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
|
||||||
PyErr_SetString(PyExc_PythonFinalizationError,
|
PyErr_SetString(PyExc_PythonFinalizationError,
|
||||||
"can't create new thread at interpreter shutdown");
|
"can't create new thread at interpreter shutdown");
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -7842,7 +7842,7 @@ os_fork1_impl(PyObject *module)
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
if (interp->finalizing) {
|
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
|
||||||
PyErr_SetString(PyExc_PythonFinalizationError,
|
PyErr_SetString(PyExc_PythonFinalizationError,
|
||||||
"can't fork at interpreter shutdown");
|
"can't fork at interpreter shutdown");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -7886,7 +7886,7 @@ os_fork_impl(PyObject *module)
|
||||||
{
|
{
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
if (interp->finalizing) {
|
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
|
||||||
PyErr_SetString(PyExc_PythonFinalizationError,
|
PyErr_SetString(PyExc_PythonFinalizationError,
|
||||||
"can't fork at interpreter shutdown");
|
"can't fork at interpreter shutdown");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -8719,7 +8719,7 @@ os_forkpty_impl(PyObject *module)
|
||||||
pid_t pid;
|
pid_t pid;
|
||||||
|
|
||||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
if (interp->finalizing) {
|
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
|
||||||
PyErr_SetString(PyExc_PythonFinalizationError,
|
PyErr_SetString(PyExc_PythonFinalizationError,
|
||||||
"can't fork at interpreter shutdown");
|
"can't fork at interpreter shutdown");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
@ -505,7 +505,7 @@ unicode_check_encoding_errors(const char *encoding, const char *errors)
|
||||||
|
|
||||||
/* Disable checks during Python finalization. For example, it allows to
|
/* Disable checks during Python finalization. For example, it allows to
|
||||||
call _PyObject_Dump() during finalization for debugging purpose. */
|
call _PyObject_Dump() during finalization for debugging purpose. */
|
||||||
if (interp->finalizing) {
|
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue