Skip to content

Commit c2ebedb

Browse files
committed
pythongh-116522: Stop the world before fork() and during shutdown
This changes the free-threaded build to perform a stop-the-world pause before deleting other thread states when forking and during shutdown. This fixes some crashes when using multiprocessing and during shutdown when running with `PYTHON_GIL=0`. This also changes `PyOS_BeforeFork` to acquire the runtime lock (i.e., `HEAD_LOCK(&_PyRuntime)`) before forking to ensure that data protected by the runtime lock (and not just the GIL or stop-the-world) is in a consistent state before forking.
1 parent 2731913 commit c2ebedb

File tree

3 files changed

+18
-3
lines changed

3 files changed

+18
-3
lines changed

Modules/posixmodule.c

+9-3
Original file line numberDiff line numberDiff line change
@@ -613,11 +613,16 @@ PyOS_BeforeFork(void)
613613
run_at_forkers(interp->before_forkers, 1);
614614

615615
_PyImport_AcquireLock(interp);
616+
_PyEval_StopTheWorldAll(&_PyRuntime);
617+
HEAD_LOCK(&_PyRuntime);
616618
}
617619

618620
void
619621
PyOS_AfterFork_Parent(void)
620622
{
623+
HEAD_UNLOCK(&_PyRuntime);
624+
_PyEval_StartTheWorldAll(&_PyRuntime);
625+
621626
PyInterpreterState *interp = _PyInterpreterState_GET();
622627
if (_PyImport_ReleaseLock(interp) <= 0) {
623628
Py_FatalError("failed releasing import lock after fork");
@@ -632,6 +637,7 @@ PyOS_AfterFork_Child(void)
632637
PyStatus status;
633638
_PyRuntimeState *runtime = &_PyRuntime;
634639

640+
// re-creates runtime->interpreters.mutex (HEAD_UNLOCK)
635641
status = _PyRuntimeState_ReInitThreads(runtime);
636642
if (_PyStatus_EXCEPTION(status)) {
637643
goto fatal_error;
@@ -7858,9 +7864,9 @@ os_fork1_impl(PyObject *module)
78587864
/* child: this clobbers and resets the import lock. */
78597865
PyOS_AfterFork_Child();
78607866
} else {
7861-
warn_about_fork_with_threads("fork1");
78627867
/* parent: release the import lock. */
78637868
PyOS_AfterFork_Parent();
7869+
warn_about_fork_with_threads("fork1");
78647870
}
78657871
if (pid == -1) {
78667872
errno = saved_errno;
@@ -7906,9 +7912,9 @@ os_fork_impl(PyObject *module)
79067912
/* child: this clobbers and resets the import lock. */
79077913
PyOS_AfterFork_Child();
79087914
} else {
7909-
warn_about_fork_with_threads("fork");
79107915
/* parent: release the import lock. */
79117916
PyOS_AfterFork_Parent();
7917+
warn_about_fork_with_threads("fork");
79127918
}
79137919
if (pid == -1) {
79147920
errno = saved_errno;
@@ -8737,9 +8743,9 @@ os_forkpty_impl(PyObject *module)
87378743
/* child: this clobbers and resets the import lock. */
87388744
PyOS_AfterFork_Child();
87398745
} else {
8740-
warn_about_fork_with_threads("forkpty");
87418746
/* parent: release the import lock. */
87428747
PyOS_AfterFork_Parent();
8748+
warn_about_fork_with_threads("forkpty");
87438749
}
87448750
if (pid == -1) {
87458751
return posix_error();

Python/pylifecycle.c

+3
Original file line numberDiff line numberDiff line change
@@ -1918,6 +1918,9 @@ Py_FinalizeEx(void)
19181918
runtime->initialized = 0;
19191919
runtime->core_initialized = 0;
19201920

1921+
/* Ensure that remaining threads are detached */
1922+
_PyEval_StopTheWorldAll(runtime);
1923+
19211924
// XXX Call something like _PyImport_Disable() here?
19221925

19231926
/* Destroy the state of all threads of the interpreter, except of the

Python/pystate.c

+6
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,10 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate)
17151715
PyInterpreterState *interp = tstate->interp;
17161716
_PyRuntimeState *runtime = interp->runtime;
17171717

1718+
#ifdef Py_GIL_DISABLED
1719+
assert(runtime->stoptheworld.world_stopped);
1720+
#endif
1721+
17181722
HEAD_LOCK(runtime);
17191723
/* Remove all thread states, except tstate, from the linked list of
17201724
thread states. This will allow calling PyThreadState_Clear()
@@ -1733,6 +1737,8 @@ _PyThreadState_DeleteExcept(PyThreadState *tstate)
17331737
interp->threads.head = tstate;
17341738
HEAD_UNLOCK(runtime);
17351739

1740+
_PyEval_StartTheWorldAll(runtime);
1741+
17361742
/* Clear and deallocate all stale thread states. Even if this
17371743
executes Python code, we should be safe since it executes
17381744
in the current thread, not one of the stale threads. */

0 commit comments

Comments
 (0)