Skip to content

Commit

Permalink
pythongh-113964: Don't prevent new threads until all non-daemon threa…
Browse files Browse the repository at this point in the history
…ds exit

Starting in Python 3.12, we started preventing 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.
  • Loading branch information
colesbury committed Mar 12, 2024
1 parent bb66600 commit 9be9d06
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 6 deletions.
24 changes: 24 additions & 0 deletions Lib/test/test_threading.py
Expand Up @@ -1340,6 +1340,30 @@ def main():
rc, out, err = assert_python_ok('-c', script)
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, b"OK\n")
self.assertEqual(rc, 0)

@skip_unless_reliable_fork
def test_reinit_tls_after_fork(self):
# Issue #13817: fork() would deadlock in a multithreaded program with
Expand Down
4 changes: 3 additions & 1 deletion Modules/_posixsubprocess.c
Expand Up @@ -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);

PyInterpreterState *interp = _PyInterpreterState_GET();
if ((preexec_fn != Py_None) && interp->finalizing) {
if ((preexec_fn != Py_None) &&
_PyInterpreterState_GetFinalizing(interp) != NULL)
{
PyErr_SetString(PyExc_PythonFinalizationError,
"preexec_fn not supported at interpreter shutdown");
return NULL;
Expand Down
2 changes: 1 addition & 1 deletion Modules/_threadmodule.c
Expand Up @@ -1395,7 +1395,7 @@ do_start_new_thread(thread_module_state* state,
"thread is not supported for isolated subinterpreters");
return -1;
}
if (interp->finalizing) {
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
PyErr_SetString(PyExc_PythonFinalizationError,
"can't create new thread at interpreter shutdown");
return -1;
Expand Down
6 changes: 3 additions & 3 deletions Modules/posixmodule.c
Expand Up @@ -7842,7 +7842,7 @@ os_fork1_impl(PyObject *module)
pid_t pid;

PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->finalizing) {
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
PyErr_SetString(PyExc_PythonFinalizationError,
"can't fork at interpreter shutdown");
return NULL;
Expand Down Expand Up @@ -7886,7 +7886,7 @@ os_fork_impl(PyObject *module)
{
pid_t pid;
PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->finalizing) {
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
PyErr_SetString(PyExc_PythonFinalizationError,
"can't fork at interpreter shutdown");
return NULL;
Expand Down Expand Up @@ -8719,7 +8719,7 @@ os_forkpty_impl(PyObject *module)
pid_t pid;

PyInterpreterState *interp = _PyInterpreterState_GET();
if (interp->finalizing) {
if (_PyInterpreterState_GetFinalizing(interp) != NULL) {
PyErr_SetString(PyExc_PythonFinalizationError,
"can't fork at interpreter shutdown");
return NULL;
Expand Down
2 changes: 1 addition & 1 deletion Objects/unicodeobject.c
Expand Up @@ -505,7 +505,7 @@ unicode_check_encoding_errors(const char *encoding, const char *errors)

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

Expand Down

0 comments on commit 9be9d06

Please sign in to comment.