Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-126016: Force crossinterpreter thread states to get cleaned up #126026

Closed
wants to merge 11 commits into from
3 changes: 3 additions & 0 deletions Include/internal/pycore_pystate.h
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ extern PyThreadState * _PyThreadState_Swap(

extern PyStatus _PyInterpreterState_Enable(_PyRuntimeState *runtime);

extern PyThreadState *
_PyThreadState_SwapAttached(PyThreadState *tstate);

#ifdef HAVE_FORK
extern PyStatus _PyInterpreterState_DeleteExceptMain(_PyRuntimeState *runtime);
extern void _PySignal_AfterFork(void);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed crash upon using CTRL+C while joining a thread containing a subinterpreter.
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
42 changes: 42 additions & 0 deletions Python/crossinterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,47 @@ _PyXI_HasCapturedException(_PyXI_session *session)
return session->error != NULL;
}

static int
_PyXI_ThreadStateRecovery(void *ptr)
ZeroIntensity marked this conversation as resolved.
Show resolved Hide resolved
{
/*
* GH-126016: If the subinterpreter was running in a
* thread, and joining that thread was interrupted (namely with CTRL+C), then
* the thread state isn't cleaned up. This forces it to get cleaned up
* upon finalization.
*/
if ((&_PyRuntime)->_main_interpreter.finalizing != 1)
{
// If the interpreter isn't finalizing, then
// it's not our job to clean anything up.
return 0;
}

PyThreadState *interp_tstate = (PyThreadState *)ptr;
if (interp_tstate->interp == NULL)
{
// Interpreter was cleaned up, do nothing.
return 0;
}

if (!_PyInterpreterState_IsRunningMain(interp_tstate->interp))
{
// Main thread was cleaned up, nothing to fix.
return 0;
}

// Subinterpreter is in a thread that suspended early!
// Get rid of this thread state or else finalize_subinterpreters() won't be
// happy.
PyThreadState *return_tstate = _PyThreadState_SwapAttached(interp_tstate);
_PyInterpreterState_SetNotRunningMain(interp_tstate->interp);

PyThreadState_Clear(interp_tstate);
PyThreadState_Swap(return_tstate);
PyThreadState_Delete(interp_tstate);
return 0;
}

int
_PyXI_Enter(_PyXI_session *session,
PyInterpreterState *interp, PyObject *nsupdates)
Expand All @@ -1718,6 +1759,7 @@ _PyXI_Enter(_PyXI_session *session,
errcode = _PyXI_ERR_ALREADY_RUNNING;
goto error;
}
Py_AddPendingCall(_PyXI_ThreadStateRecovery, _PyThreadState_GET());
session->running = 1;

// Cache __main__.__dict__.
Expand Down
13 changes: 13 additions & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,19 @@ PyThreadState_Swap(PyThreadState *newts)
return _PyThreadState_Swap(&_PyRuntime, newts);
}

/*
* Calls PyThreadState_Swap() on the a bound thread state.
* This breaks the GIL, so it should only be used if certain that
* it's impossible for the thread to be running code.
*/
PyThreadState *
_PyThreadState_SwapAttached(PyThreadState *tstate)
{
tstate->_status.bound_gilstate = 0;
tstate->_status.holds_gil = 0;
tstate->_status.active = 0;
return PyThreadState_Swap(tstate);
}

void
_PyThreadState_Bind(PyThreadState *tstate)
Expand Down
Loading