Skip to content
Next Next commit
Delete state of new thread on its startup failure
If Python fails to start newly created thread
due to failure of underlying PyThread_start_new_thread() call,
its state should be removed from interpreter' thread states list
to avoid its double cleanup

* make 'PyThreadState_Delete' accept unbound thread state
* in 'thread_PyThread_start_new_thread', delete state of new thread from interpreter if 'PyThread_start_new_thread' call fails
  • Loading branch information
chgnrdv committed Sep 22, 2023
commit 788b0b6c8c85eb8e9037b306e92078d218450c39
20 changes: 20 additions & 0 deletions Lib/test/test_threading.py
Original file line number Diff line number Diff line change
Expand Up @@ -1045,6 +1045,26 @@ def exit_handler():
self.assertEqual(out, b'')
self.assertIn(b"can't create new thread at interpreter shutdown", err)

def test_start_new_thread_failed(self):
# gh-109746: if Python fails to start newly created thread
# due to failure of underlying PyThread_start_new_thread() call,
# its state should be removed from interpreter' thread states list
# to avoid its double cleanup
code = """if 1:
import resource
import threading

resource.setrlimit(resource.RLIMIT_NPROC, (150, 150))
Comment thread
serhiy-storchaka marked this conversation as resolved.
Outdated

while True:
t = threading.Thread()
t.start()
t.join()
"""
_, out, err = assert_python_failure("-c", code)
self.assertEqual(out, b'')
self.assertIn(b"can't start new thread", err)

class ThreadJoinOnShutdown(BaseTestCase):

def _run_and_join(self, script):
Expand Down
1 change: 1 addition & 0 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,7 @@ thread_PyThread_start_new_thread(PyObject *self, PyObject *fargs)
if (ident == PYTHREAD_INVALID_THREAD_ID) {
PyErr_SetString(ThreadError, "can't start new thread");
PyThreadState_Clear(boot->tstate);
PyThreadState_Delete(boot->tstate);
thread_bootstate_free(boot, 1);
return NULL;
}
Expand Down
4 changes: 3 additions & 1 deletion Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -1589,7 +1589,9 @@ tstate_delete_common(PyThreadState *tstate)
if (tstate->_status.bound_gilstate) {
unbind_gilstate_tstate(tstate);
}
unbind_tstate(tstate);
if (tstate->_status.bound) {
unbind_tstate(tstate);
}

// XXX Move to PyThreadState_Clear()?
clear_datastack(tstate);
Expand Down