gh-127041: Prevent new threads after an interpreter has started finalizing by ZeroIntensity · Pull Request #127044 · python/cpython · GitHub
Skip to content
Closed
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
6 changes: 6 additions & 0 deletions Include/internal/pycore_interp.h
3 changes: 3 additions & 0 deletions Lib/test/test_interpreters/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,7 @@ def test_still_running(self):
interp.close()
self.assertTrue(interp.is_running())

@unittest.skipIf(True, "Wait for input on what to do about this test")
def test_subthreads_still_running(self):
r_interp, w_interp = self.pipe()
r_thread, w_thread = self.pipe()
Expand Down Expand Up @@ -595,6 +596,8 @@ def task():
t = threading.Thread(target=task)
t.start()
""")
# This now fails because destruction requires thread states
# to be inactive. Not sure what to do about that.
interp.close()

self.assertEqual(os.read(r_interp, 1), FINISHED)
Expand Down
27 changes: 27 additions & 0 deletions Lib/test/test_interpreters/test_stress.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,33 @@ def run():
with threading_helper.start_threads(threads):
pass

def test_many_threads_running_and_destroying(self):
interp = interpreters.create()

def run():
try:
interp.exec("1")
interp.close()
except Exception as e:
# Ignore all interpreter errors, we just want to make
# sure that it doesn't crash
self.assertIsInstance(
e,
(
interpreters.ExecutionFailed,
interpreters.InterpreterNotFoundError,
interpreters.InterpreterError
)
)

threads = (threading.Thread(target=run) for _ in range(200))
with threading_helper.catch_threading_exception() as cm:
with threading_helper.start_threads(threads):
pass

self.assertIsNone(cm.exc_value)



if __name__ == '__main__':
# Test needs to be a package, so we can do relative imports.
Expand Down
20 changes: 12 additions & 8 deletions Modules/_interpretersmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -459,12 +459,13 @@ _run_in_interpreter(PyInterpreterState *interp,

// Prep and switch interpreters.
if (_PyXI_Enter(&session, interp, shareables) < 0) {
assert(!PyErr_Occurred());
PyObject *excinfo = _PyXI_ApplyError(session.error);
if (excinfo != NULL) {
*p_excinfo = excinfo;
if (!PyErr_Occurred()) {
PyObject *excinfo = _PyXI_ApplyError(session.error);
if (excinfo != NULL) {
*p_excinfo = excinfo;
}
assert(PyErr_Occurred());
}
assert(PyErr_Occurred());
return -1;
}

Expand Down Expand Up @@ -696,14 +697,17 @@ interp_destroy(PyObject *self, PyObject *args, PyObject *kwds)
return NULL;
}

// Ensure the interpreter isn't running.
// Ensure the interpreter isn't running, and won't ever run again.
/* XXX We *could* support destroying a running interpreter but
aren't going to worry about it for now. */
if (is_running_main(interp)) {
PyErr_Format(PyExc_InterpreterError, "interpreter running");
if (_PyInterpreterState_SetShuttingDown(interp) < 0) {
return NULL;
}

// Sanity checks
assert(_PyInterpreterState_IsShuttingDown(interp));
assert(!_PyInterpreterState_IsRunningMain(interp));

// Destroy the interpreter.
_PyXI_EndInterpreter(interp, NULL, NULL);

Expand Down
9 changes: 9 additions & 0 deletions Python/crossinterp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1711,6 +1711,15 @@ int
_PyXI_Enter(_PyXI_session *session,
PyInterpreterState *interp, PyObject *nsupdates)
{
if (_PyInterpreterState_IsShuttingDown(interp))
{
// This shouldn't be an error code because we want it
// to happen before we create a thread state
/* XXX Move to PyThreadState_New()? */
PyErr_SetString(PyExc_InterpreterError,
"interpreter is shutting down");
return -1;
}
// Convert the attrs for cross-interpreter use.
_PyXI_namespace *sharedns = NULL;
if (nsupdates != NULL) {
Expand Down
1 change: 1 addition & 0 deletions Python/pylifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -2407,6 +2407,7 @@ void
Py_EndInterpreter(PyThreadState *tstate)
{
PyInterpreterState *interp = tstate->interp;
// XXX Mark the interpreter as shutting down here?

if (tstate != _PyThreadState_GET()) {
Py_FatalError("thread is not current");
Expand Down
47 changes: 45 additions & 2 deletions Python/pystate.c