Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ extern "C" {
// transitions between "attached" and "detached" on its own PyThreadState.
//
// The "suspended" states are used to implement stop-the-world pauses and to
// merge biased reference counts on behalf of detached threads. They are only
// used in `--disable-gil` builds.
// merge biased reference counts on behalf of detached threads. They are
// used for global pauses in all builds and per-interpreter pauses in
// `--disable-gil` builds.
// They are similar to the "detached" state in that the thread is not allowed
// to call most Python APIs. A suspended thread trying to attach marks itself
// as "suspended-waiting". Only the thread responsible for suspending it may
Expand Down Expand Up @@ -161,12 +162,12 @@ extern void _PyThreadState_Suspend(PyThreadState *tstate);
// stop the world while holding the thread in the suspended state.
// Returns 1 on success, 0 if the thread was not in the "detached" state.
extern int _PyThreadState_TrySuspendDetached(PyThreadState *tstate);
#endif

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

// Mark the thread state as "shutting down". This is used during interpreter
// and runtime finalization. The thread may no longer attach to the
Expand Down Expand Up @@ -245,7 +246,9 @@ extern void _PyThreadState_Bind(PyThreadState *tstate);
PyAPI_FUNC(PyThreadState *) _PyThreadState_NewBound(
PyInterpreterState *interp,
int whence);
extern PyThreadState * _PyThreadState_RemoveExcept(PyThreadState *tstate);
extern PyThreadState * _PyThreadState_RemoveExcept(
PyThreadState *tstate,
int is_after_fork);
extern void _PyThreadState_DeleteList(PyThreadState *list, int is_after_fork);
extern void _PyThreadState_ClearMimallocHeaps(PyThreadState *tstate);

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

#ifdef HAVE_FORK
extern PyStatus _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime);
extern void _PySignal_AfterFork(void);
#endif

Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_qsbr.h
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ extern void
_Py_qsbr_fini(PyInterpreterState *interp);

extern void
_Py_qsbr_after_fork(struct _PyThreadStateImpl *tstate);
_Py_qsbr_after_fork(PyInterpreterState *interp);

#ifdef __cplusplus
}
Expand Down
22 changes: 22 additions & 0 deletions Lib/test/test_interpreters/test_stress.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import os
import threading
import unittest

from test import support
from test.support import import_helper
from test.support import threading_helper
from test.support import warnings_helper
# Raise SkipTest if subinterpreters not supported.
_interpreters = import_helper.import_module('_interpreters')
from concurrent import interpreters
Expand Down Expand Up @@ -82,6 +84,26 @@ def test_create_interpreter_no_memory(self):
with support.inject_memory_error_cm(0, 1):
_interpreters.create()

@warnings_helper.ignore_fork_in_thread_deprecation_warnings()
@support.requires_fork()
def test_unclosed_interpreter_on_fork(self):
interp = interpreters.create()
interp.exec("pass")

fds = os.pipe()
pid = os.fork()

if pid == 0:
os.close(fds[0])
os.write(fds[1], b"OK")
os._exit(0)
else:
os.close(fds[1])
self.addCleanup(os.close, fds[0])
self.addCleanup(support.wait_process, pid, exitcode=0)
value = os.read(fds[0], 100)
self.assertEqual(value, b"OK")


if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix crashes and hangs when calling :func:`os.fork` with existing
subinterpreters.
101 changes: 79 additions & 22 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,9 @@ PyOS_BeforeFork(void)
_PyImport_AcquireLock(interp);
_PyEval_StopTheWorldAll(&_PyRuntime);
HEAD_LOCK(&_PyRuntime);
_PyWarnings_BeforeFork(interp);
for (PyInterpreterState *i = PyInterpreterState_Head(); i != NULL; i = i->next) {
_PyWarnings_BeforeFork(i);
}
}

void
Expand Down Expand Up @@ -753,6 +755,78 @@ reset_asyncio_state(_PyThreadStateImpl *tstate)
tstate->asyncio_running_task = NULL;
}

static PyStatus
clear_threads_after_fork(PyThreadState *tstate)
{
struct after_fork_state {
PyThreadState *tstate;
PyThreadState *deleted;
PyThreadState *running_main;
};

size_t count = 0;
for (PyInterpreterState *interp = PyInterpreterState_Head(); interp != NULL;
interp = interp->next) {
count++;
}
struct after_fork_state *states = PyMem_RawCalloc(count, sizeof(*states));
if (states == NULL) {
return _PyStatus_NO_MEMORY();
}

// Unlink all vanished threads before running any finalizers. Keep the
// subinterpreters busy until their dead thread states have been cleared.
size_t next = 1;
for (PyInterpreterState *interp = PyInterpreterState_Head(); interp != NULL;
interp = interp->next) {
size_t index = (interp == tstate->interp) ? 0 : next++;
struct after_fork_state *state = &states[index];
state->running_main = interp->threads.main;
if (state->running_main != NULL
&& state->running_main->thread_id != tstate->thread_id) {
state->running_main = NULL;
}
state->tstate = (index == 0) ? tstate : PyThreadState_New(interp);
if (state->tstate == NULL) {
PyMem_RawFree(states);
return _PyStatus_NO_MEMORY();
}
state->deleted = _PyThreadState_RemoveExcept(state->tstate,
/*is_after_fork=*/1);
if (index != 0) {
_Py_atomic_store_ptr(&interp->threads.main, state->tstate);
}
Py_ssize_t thread_count = 0;
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
#ifdef PY_HAVE_THREAD_NATIVE_ID
p->native_thread_id = tstate->native_thread_id;
#endif
if (p->_whence == _PyThreadState_WHENCE_THREADING
|| p->_whence == _PyThreadState_WHENCE_THREADING_DAEMON) {
thread_count++;
}
}
_Py_atomic_store_ssize(&interp->threads.count, thread_count);
}

// Clearing thread states can call destructors, so resume before doing so.
_PyEval_StartTheWorldAll(&_PyRuntime);
_PyThreadState_DeleteList(states[0].deleted, /*is_after_fork=*/1);
for (size_t i = 1; i < count; i++) {
struct after_fork_state *state = &states[i];
PyInterpreterState *interp = state->tstate->interp;
PyThreadState_Swap(state->tstate);
_PyThreadState_DeleteList(state->deleted, /*is_after_fork=*/1);
run_at_forkers(interp->after_forkers_child, 0);
_Py_atomic_store_ptr(&interp->threads.main, state->running_main);
PyThreadState_Clear(state->tstate);
PyThreadState_Swap(tstate);
PyThreadState_Delete(state->tstate);
}
PyMem_RawFree(states);
return _PyStatus_OK();
}


void
PyOS_AfterFork_Child(void)
Expand All @@ -774,11 +848,6 @@ PyOS_AfterFork_Child(void)
tstate->native_thread_id = PyThread_get_thread_native_id();
#endif

#ifdef Py_GIL_DISABLED
_Py_brc_after_fork(tstate->interp);
_Py_qsbr_after_fork((_PyThreadStateImpl *)tstate);
#endif

// Ideally we could guarantee tstate is running main.
_PyInterpreterState_ReinitRunningMain(tstate);

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

// Thread-state cleanup can run finalizers that issue warnings.
_PyWarnings_AfterFork(tstate->interp);

reset_remotedebug_data(tstate);

reset_asyncio_state((_PyThreadStateImpl *)tstate);

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

_PyImport_ReInitLock(tstate->interp);
_PyImport_ReleaseLock(tstate->interp);

_PySignal_AfterFork();

status = _PyInterpreterState_DeleteExceptMain(runtime);
if (_PyStatus_EXCEPTION(status)) {
goto fatal_error;
}
assert(_PyThreadState_GET() == tstate);

status = _PyPerfTrampoline_AfterFork_Child();
if (_PyStatus_EXCEPTION(status)) {
goto fatal_error;
Expand Down
22 changes: 11 additions & 11 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -618,24 +618,24 @@ PyEval_ReleaseThread(PyThreadState *tstate)
}

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

struct _gil_runtime_state *gil = tstate->interp->ceval.gil;
if (!gil_created(gil)) {
return _PyStatus_OK();
for (PyInterpreterState *interp = PyInterpreterState_Head(); interp != NULL;
interp = interp->next) {
struct _gil_runtime_state *gil = interp->ceval.gil;
if (interp->ceval.own_gil && gil_created(gil)) {
recreate_gil(gil);
}
_Py_FOR_EACH_TSTATE_UNLOCKED(interp, p) {
p->holds_gil = 0;
p->gil_requested = 0;
}
}
recreate_gil(gil);

take_gil(tstate);

struct _pending_calls *pending = &tstate->interp->ceval.pending;
_PyMutex_at_fork_reinit(&pending->mutex);

return _PyStatus_OK();
}
#endif
Expand Down
9 changes: 4 additions & 5 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -2357,9 +2357,8 @@ make_pre_finalization_calls(PyThreadState *tstate, int subinterpreters)
}

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

PyThreadState *list = _PyThreadState_RemoveExcept(tstate);
PyThreadState *list = _PyThreadState_RemoveExcept(tstate, /*is_after_fork=*/0);
for (PyThreadState *p = list; p != NULL; p = p->next) {
_PyThreadState_SetShuttingDown(p);
}
Expand Down
Loading
Loading