Skip to content

Commit 2baafff

Browse files
committed
Reset subinterpreters after fork instead of deleting them.
1 parent fd8f475 commit 2baafff

8 files changed

Lines changed: 188 additions & 129 deletions

File tree

‎Include/internal/pycore_pystate.h‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ extern "C" {
2222
// transitions between "attached" and "detached" on its own PyThreadState.
2323
//
2424
// The "suspended" states are used to implement stop-the-world pauses and to
25-
// merge biased reference counts on behalf of detached threads. They are only
26-
// used in `--disable-gil` builds.
25+
// merge biased reference counts on behalf of detached threads. They are
26+
// used for global pauses in all builds and per-interpreter pauses in
27+
// `--disable-gil` builds.
2728
// They are similar to the "detached" state in that the thread is not allowed
2829
// to call most Python APIs. A suspended thread trying to attach marks itself
2930
// as "suspended-waiting". Only the thread responsible for suspending it may
@@ -161,12 +162,12 @@ extern void _PyThreadState_Suspend(PyThreadState *tstate);
161162
// stop the world while holding the thread in the suspended state.
162163
// Returns 1 on success, 0 if the thread was not in the "detached" state.
163164
extern int _PyThreadState_TrySuspendDetached(PyThreadState *tstate);
165+
#endif
164166

165167
// Resume a thread suspended by _PyThreadState_TrySuspendDetached() or a
166168
// stop-the-world pause: switch it back to "detached" or "detached-waiting"
167169
// and wake it if it is waiting to attach.
168170
extern void _PyThreadState_ResumeDetached(PyThreadState *tstate);
169-
#endif
170171

171172
// Mark the thread state as "shutting down". This is used during interpreter
172173
// and runtime finalization. The thread may no longer attach to the
@@ -245,7 +246,9 @@ extern void _PyThreadState_Bind(PyThreadState *tstate);
245246
PyAPI_FUNC(PyThreadState *) _PyThreadState_NewBound(
246247
PyInterpreterState *interp,
247248
int whence);
248-
extern PyThreadState * _PyThreadState_RemoveExcept(PyThreadState *tstate);
249+
extern PyThreadState * _PyThreadState_RemoveExcept(
250+
PyThreadState *tstate,
251+
int is_after_fork);
249252
extern void _PyThreadState_DeleteList(PyThreadState *list, int is_after_fork);
250253
extern void _PyThreadState_ClearMimallocHeaps(PyThreadState *tstate);
251254

@@ -267,7 +270,6 @@ extern PyThreadState * _PyThreadState_Swap(
267270
extern PyStatus _PyInterpreterState_Enable(_PyRuntimeState *runtime);
268271

269272
#ifdef HAVE_FORK
270-
extern PyStatus _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime);
271273
extern void _PySignal_AfterFork(void);
272274
#endif
273275

‎Include/internal/pycore_qsbr.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ extern void
180180
_Py_qsbr_fini(PyInterpreterState *interp);
181181

182182
extern void
183-
_Py_qsbr_after_fork(struct _PyThreadStateImpl *tstate);
183+
_Py_qsbr_after_fork(PyInterpreterState *interp);
184184

185185
#ifdef __cplusplus
186186
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crashes and hangs when calling :func:`os.fork` with existing
2+
subinterpreters.

‎Modules/posixmodule.c‎

Lines changed: 79 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,9 @@ PyOS_BeforeFork(void)
723723
_PyImport_AcquireLock(interp);
724724
_PyEval_StopTheWorldAll(&_PyRuntime);
725725
HEAD_LOCK(&_PyRuntime);
726-
_PyWarnings_BeforeFork(interp);
726+
for (PyInterpreterState *i = PyInterpreterState_Head(); i != NULL; i = i->next) {
727+
_PyWarnings_BeforeFork(i);
728+
}
727729
}
728730

729731
void
@@ -753,6 +755,78 @@ reset_asyncio_state(_PyThreadStateImpl *tstate)
753755
tstate->asyncio_running_task = NULL;
754756
}
755757

758+
static PyStatus
759+
clear_threads_after_fork(PyThreadState *tstate)
760+
{
761+
struct after_fork_state {
762+
PyThreadState *tstate;
763+
PyThreadState *deleted;
764+
PyThreadState *running_main;
765+
};
766+
767+
size_t count = 0;
768+
for (PyInterpreterState *interp = PyInterpreterState_Head(); interp != NULL;
769+
interp = interp->next) {
770+
count++;
771+
}
772+
struct after_fork_state *states = PyMem_RawCalloc(count, sizeof(*states));
773+
if (states == NULL) {
774+
return _PyStatus_NO_MEMORY();
775+
}
776+
777+
// Unlink all vanished threads before running any finalizers. Keep the
778+
// subinterpreters busy until their dead thread states have been cleared.
779+
size_t next = 1;
780+
for (PyInterpreterState *interp = PyInterpreterState_Head(); interp != NULL;
781+
interp = interp->next) {
782+
size_t index = (interp == tstate->interp) ? 0 : next++;
783+
struct after_fork_state *state = &states[index];
784+
state->running_main = interp->threads.main;
785+
if (state->running_main != NULL
786+
&& state->running_main->thread_id != tstate->thread_id) {
787+
state->running_main = NULL;
788+
}
789+
state->tstate = (index == 0) ? tstate : PyThreadState_New(interp);
790+
if (state->tstate == NULL) {
791+
PyMem_RawFree(states);
792+
return _PyStatus_NO_MEMORY();
793+
}
794+
state->deleted = _PyThreadState_RemoveExcept(state->tstate,
795+
/*is_after_fork=*/1);
796+
if (index != 0) {
797+
_Py_atomic_store_ptr(&interp->threads.main, state->tstate);
798+
}
799+
Py_ssize_t thread_count = 0;
800+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
801+
#ifdef PY_HAVE_THREAD_NATIVE_ID
802+
p->native_thread_id = tstate->native_thread_id;
803+
#endif
804+
if (p->_whence == _PyThreadState_WHENCE_THREADING
805+
|| p->_whence == _PyThreadState_WHENCE_THREADING_DAEMON) {
806+
thread_count++;
807+
}
808+
}
809+
_Py_atomic_store_ssize(&interp->threads.count, thread_count);
810+
}
811+
812+
// Clearing thread states can call destructors, so resume before doing so.
813+
_PyEval_StartTheWorldAll(&_PyRuntime);
814+
_PyThreadState_DeleteList(states[0].deleted, /*is_after_fork=*/1);
815+
for (size_t i = 1; i < count; i++) {
816+
struct after_fork_state *state = &states[i];
817+
PyInterpreterState *interp = state->tstate->interp;
818+
PyThreadState_Swap(state->tstate);
819+
_PyThreadState_DeleteList(state->deleted, /*is_after_fork=*/1);
820+
run_at_forkers(interp->after_forkers_child, 0);
821+
_Py_atomic_store_ptr(&interp->threads.main, state->running_main);
822+
PyThreadState_Clear(state->tstate);
823+
PyThreadState_Swap(tstate);
824+
PyThreadState_Delete(state->tstate);
825+
}
826+
PyMem_RawFree(states);
827+
return _PyStatus_OK();
828+
}
829+
756830

757831
void
758832
PyOS_AfterFork_Child(void)
@@ -774,11 +848,6 @@ PyOS_AfterFork_Child(void)
774848
tstate->native_thread_id = PyThread_get_thread_native_id();
775849
#endif
776850

777-
#ifdef Py_GIL_DISABLED
778-
_Py_brc_after_fork(tstate->interp);
779-
_Py_qsbr_after_fork((_PyThreadStateImpl *)tstate);
780-
#endif
781-
782851
// Ideally we could guarantee tstate is running main.
783852
_PyInterpreterState_ReinitRunningMain(tstate);
784853

@@ -794,32 +863,20 @@ PyOS_AfterFork_Child(void)
794863
_Py_jit_debug_mutex = (PyMutex){0};
795864
#endif
796865

797-
// Thread-state cleanup can run finalizers that issue warnings.
798-
_PyWarnings_AfterFork(tstate->interp);
799-
800866
reset_remotedebug_data(tstate);
801867

802868
reset_asyncio_state((_PyThreadStateImpl *)tstate);
803869

804-
// Remove the dead thread states. We "start the world" once we are the only
805-
// thread state left to undo the stop the world call in `PyOS_BeforeFork`.
806-
// That needs to happen before `_PyThreadState_DeleteList`, because that
807-
// may call destructors.
808-
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
809-
_PyEval_StartTheWorldAll(&_PyRuntime);
810-
_PyThreadState_DeleteList(list, /*is_after_fork=*/1);
870+
status = clear_threads_after_fork(tstate);
871+
if (_PyStatus_EXCEPTION(status)) {
872+
goto fatal_error;
873+
}
811874

812875
_PyImport_ReInitLock(tstate->interp);
813876
_PyImport_ReleaseLock(tstate->interp);
814877

815878
_PySignal_AfterFork();
816879

817-
status = _PyInterpreterState_DeleteExceptMain(runtime);
818-
if (_PyStatus_EXCEPTION(status)) {
819-
goto fatal_error;
820-
}
821-
assert(_PyThreadState_GET() == tstate);
822-
823880
status = _PyPerfTrampoline_AfterFork_Child();
824881
if (_PyStatus_EXCEPTION(status)) {
825882
goto fatal_error;

‎Python/ceval_gil.c‎

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -618,24 +618,26 @@ PyEval_ReleaseThread(PyThreadState *tstate)
618618
}
619619

620620
#ifdef HAVE_FORK
621-
/* This function is called from PyOS_AfterFork_Child to re-initialize the
622-
GIL and pending calls lock. */
621+
/* This function is called from PyOS_AfterFork_Child to re-initialize all GILs. */
623622
PyStatus
624623
_PyEval_ReInitThreads(PyThreadState *tstate)
625624
{
626625
assert(tstate->interp == _PyInterpreterState_Main());
627626

628-
struct _gil_runtime_state *gil = tstate->interp->ceval.gil;
629-
if (!gil_created(gil)) {
630-
return _PyStatus_OK();
627+
for (PyInterpreterState *interp = PyInterpreterState_Head(); interp != NULL;
628+
interp = interp->next) {
629+
struct _gil_runtime_state *gil = interp->ceval.gil;
630+
if (interp->ceval.own_gil && gil_created(gil)) {
631+
recreate_gil(gil);
632+
}
633+
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
634+
p->holds_gil = 0;
635+
p->gil_requested = 0;
636+
}
631637
}
632-
recreate_gil(gil);
633638

634639
take_gil(tstate);
635640

636-
struct _pending_calls *pending = &tstate->interp->ceval.pending;
637-
_PyMutex_at_fork_reinit(&pending->mutex);
638-
639641
return _PyStatus_OK();
640642
}
641643
#endif

‎Python/pylifecycle.c‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2357,9 +2357,8 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters)
23572357
}
23582358

23592359
/* Stop the world to prevent other threads from creating threads or
2360-
* atexit callbacks. On the default build, this is simply locked by
2361-
* the GIL. For pending calls, we acquire the dedicated mutex, because
2362-
* Py_AddPendingCall() can be called without an attached thread state.
2360+
* atexit callbacks. For pending calls, we acquire the dedicated mutex,
2361+
* because Py_AddPendingCall() can be called without an attached thread state.
23632362
*/
23642363
PyMutex_Lock(&interp->ceval.pending.mutex);
23652364
_PyEval_StopTheWorldAll(interp->runtime);
@@ -2444,7 +2443,7 @@ _Py_Finalize(_PyRuntimeState *runtime)
24442443
except if wait_for_thread_shutdown() has been cancelled by CTRL+C.
24452444
We start the world once we are the only thread state left,
24462445
before we call destructors. */
2447-
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
2446+
PyThreadState *list = _PyThreadState_RemoveExcept(tstate, /*is_after_fork=*/0);
24482447
for (PyThreadState *p = list; p != NULL; p = p->next) {
24492448
_PyThreadState_SetShuttingDown(p);
24502449
}
@@ -2825,7 +2824,7 @@ Py_EndInterpreter(PyThreadState *tstate)
28252824
when they attempt to take the GIL (ex: PyEval_RestoreThread()). */
28262825
_PyInterpreterState_SetFinalizing(interp, tstate);
28272826

2828-
PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
2827+
PyThreadState *list = _PyThreadState_RemoveExcept(tstate, /*is_after_fork=*/0);
28292828
for (PyThreadState *p = list; p != NULL; p = p->next) {
28302829
_PyThreadState_SetShuttingDown(p);
28312830
}

0 commit comments

Comments
 (0)