]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.12] gh-109746: Make _thread.start_new_thread delete state of new thread on its...
authorSerhiy Storchaka <storchaka@gmail.com>
Fri, 22 Nov 2024 19:56:39 +0000 (21:56 +0200)
committerGitHub <noreply@github.com>
Fri, 22 Nov 2024 19:56:39 +0000 (19:56 +0000)
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 <52372310+chgnrdv@users.noreply.github.com>
Lib/test/test_threading.py
Misc/NEWS.d/next/Core_and_Builtins/2023-09-22-21-01-56.gh-issue-109746.32MHt9.rst [new file with mode: 0644]
Modules/_threadmodule.c
Python/pystate.c

index 2e4b860b975ff882909570f4f7a6ebc9a2dbcac6..183af7c1f0836fb0c79908fc15573942470378ad 100644 (file)
@@ -1150,6 +1150,41 @@ class ThreadTests(BaseTestCase):
         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 (file)
index 0000000..2d350c3
--- /dev/null
@@ -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.
index 365f4460088aab0d54ddb3ab37491cf6e2547d81..518c246e98caf6d594c2b7b6c5809f5f73834ca1 100644 (file)
@@ -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;
     }
index d0651fbd592f43131778c3da60ccac43106bfd82..f0e0d4117e4259fd2aa760bbc9f40e22c407a2f9 100644 (file)
@@ -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);