Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
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.
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)
{
/*
* 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