https://github.com/python/cpython/commit/75ef92da291247fd9d21ec26bd22bf8055b5f330
commit: 75ef92da291247fd9d21ec26bd22bf8055b5f330
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-11-22T19:55:44Z
summary:

[3.13] gh-109746: Make _thread.start_new_thread delete state of new thread on 
its startup failure (GH-109761) (GH-127171)

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.

(cherry picked from commit ca3ea9ad05c3d876a58463595e5b4228fda06936)

Co-authored-by: Radislav Chugunov <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst
M Lib/test/test_threading.py
M Modules/_threadmodule.c
M Python/pystate.c

diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 329767aa82e336..7ee0b2964bdb53 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -1171,6 +1171,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 6183d0608b92b6..15b2bd3573c044 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -421,6 +421,7 @@ ThreadHandle_start(ThreadHandle *self, PyObject *func, 
PyObject *args,
     PyThread_handle_t os_handle;
     if (PyThread_start_joinable_thread(thread_run, boot, &ident, &os_handle)) {
         PyThreadState_Clear(boot->tstate);
+        PyThreadState_Delete(boot->tstate);
         thread_bootstate_free(boot, 1);
         PyErr_SetString(ThreadError, "can't start new thread");
         goto start_failed;
diff --git a/Python/pystate.c b/Python/pystate.c
index 960895e5badc27..528847d80169bf 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -1811,7 +1811,9 @@ tstate_delete_common(PyThreadState *tstate, int 
release_gil)
     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);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to