diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index 2e4b860b975ff8..183af7c1f0836f 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1150,6 +1150,41 @@ def __del__(self): self.assertEqual(out.strip(), b"OK") 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 + try: + from resource import setrlimit, RLIMIT_NPROC + except ImportError as err: + self.skipTest(err) # RLIMIT_NPROC is specific to Linux and BSD + code = """if 1: + import resource + import _thread + + def f(): + print("shouldn't be printed") + + limits = resource.getrlimit(resource.RLIMIT_NPROC) + [_, hard] = limits + resource.setrlimit(resource.RLIMIT_NPROC, (0, hard)) + + try: + _thread.start_new_thread(f, ()) + except RuntimeError: + print('ok') + else: + print('skip') + """ + _, out, err = assert_python_ok("-u", "-c", code) + out = out.strip() + if out == b'skip': + self.skipTest('RLIMIT_NPROC had no effect; probably superuser') + self.assertEqual(out, b'ok') + self.assertEqual(err, b'') + + class ThreadJoinOnShutdown(BaseTestCase): def _run_and_join(self, script): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst b/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst new file mode 100644 index 00000000000000..2d350c33aa6975 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst @@ -0,0 +1 @@ +If :func:`!_thread.start_new_thread` fails to start a new thread, it deletes its state from interpreter and thus avoids its repeated cleanup on finalization. diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c index 365f4460088aab..518c246e98caf6 100644 --- a/Modules/_threadmodule.c +++ b/Modules/_threadmodule.c @@ -1219,6 +1219,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; } diff --git a/Python/pystate.c b/Python/pystate.c index d0651fbd592f43..f0e0d4117e4259 100644 --- a/Python/pystate.c +++ b/Python/pystate.c @@ -1593,7 +1593,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);