]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-126016: Remove bad assertion in `PyThreadState_Clear` (GH-139158)
authorPeter Bierma <zintensitydev@gmail.com>
Fri, 19 Sep 2025 12:17:05 +0000 (08:17 -0400)
committerGitHub <noreply@github.com>
Fri, 19 Sep 2025 12:17:05 +0000 (12:17 +0000)
In the _interpreters module, we use PyEval_EvalCode() to run Python code in another interpreter. However, when the process receives a KeyboardInterrupt, PyEval_EvalCode() will jump straight to finalization rather than returning. This prevents us from cleaning up and marking the thread as "not running main", which triggers an assertion in PyThreadState_Clear() on debug builds. Since everything else works as intended, remove that assertion.

Lib/test/test_interpreters/test_api.py
Misc/NEWS.d/next/Library/2025-09-19-07-41-52.gh-issue-126016.Uz9W6h.rst [new file with mode: 0644]
Python/pystate.c

index 1f38a43be51e7a716e5876d80bb9dafde0a55335..8e9f1c3204a8bbe7497decf25e426c312fdd1aa8 100644 (file)
@@ -1,6 +1,7 @@
 import contextlib
 import os
 import pickle
+import signal
 import sys
 from textwrap import dedent
 import threading
@@ -11,7 +12,7 @@ from test import support
 from test.support import os_helper
 from test.support import script_helper
 from test.support import import_helper
-from test.support.script_helper import assert_python_ok
+from test.support.script_helper import assert_python_ok, spawn_python
 # Raise SkipTest if subinterpreters not supported.
 _interpreters = import_helper.import_module('_interpreters')
 from concurrent import interpreters
@@ -434,6 +435,31 @@ class InterpreterObjectTests(TestBase):
         self.assertIn(b"remaining subinterpreters", stdout)
         self.assertNotIn(b"Traceback", stdout)
 
+    @support.requires_subprocess()
+    @unittest.skipIf(os.name == 'nt', "signals don't work well on windows")
+    def test_keyboard_interrupt_in_thread_running_interp(self):
+        import subprocess
+        source = f"""if True:
+        from concurrent import interpreters
+        from threading import Thread
+
+        def test():
+            import time
+            print('a', flush=True, end='')
+            time.sleep(10)
+
+        interp = interpreters.create()
+        interp.call_in_thread(test)
+        """
+
+        with spawn_python("-c", source, stderr=subprocess.PIPE) as proc:
+            self.assertEqual(proc.stdout.read(1), b'a')
+            proc.send_signal(signal.SIGINT)
+            proc.stderr.flush()
+            error = proc.stderr.read()
+            self.assertIn(b"KeyboardInterrupt", error)
+            retcode = proc.wait()
+            self.assertEqual(retcode, 0)
 
 
 class TestInterpreterIsRunning(TestBase):
diff --git a/Misc/NEWS.d/next/Library/2025-09-19-07-41-52.gh-issue-126016.Uz9W6h.rst b/Misc/NEWS.d/next/Library/2025-09-19-07-41-52.gh-issue-126016.Uz9W6h.rst
new file mode 100644 (file)
index 0000000..feb0929
--- /dev/null
@@ -0,0 +1,2 @@
+Fix an assertion failure when sending :exc:`KeyboardInterrupt` to a Python
+process running a subinterpreter in a separate thread.
index 29c713dccc9fe895d37bdd0171e388f46d029cbb..dbed609f29aa074fcf2506cf93f7dc17cd460c55 100644 (file)
@@ -1625,7 +1625,11 @@ PyThreadState_Clear(PyThreadState *tstate)
 {
     assert(tstate->_status.initialized && !tstate->_status.cleared);
     assert(current_fast_get()->interp == tstate->interp);
-    assert(!_PyThreadState_IsRunningMain(tstate));
+    // GH-126016: In the _interpreters module, KeyboardInterrupt exceptions
+    // during PyEval_EvalCode() are sent to finalization, which doesn't let us
+    // mark threads as "not running main". So, for now this assertion is
+    // disabled.
+    // XXX assert(!_PyThreadState_IsRunningMain(tstate));
     // XXX assert(!tstate->_status.bound || tstate->_status.unbound);
     tstate->_status.finalizing = 1;  // just in case