https://github.com/python/cpython/commit/c3cb46870bdb18d104557d8b69d451a692c64c8a
commit: c3cb46870bdb18d104557d8b69d451a692c64c8a
branch: main
author: Himesh Rupchandani <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-09-29T22:24:24-04:00
summary:
gh-158364: Don't report other interpreters' threads in `sys._current_frames`
(GH-158369)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst
M Lib/test/test_sys.py
M Python/pystate.c
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index da1bd381dd182e..1ccdcf5a187bbb 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -563,6 +563,38 @@ def g456():
leave_g.set()
t.join()
+ @support.cpython_only
+ @requires_subinterpreters
+ @threading_helper.requires_working_threading()
+ def test_current_frames_other_interpreters(self):
+ # gh-158364: sys._current_frames() would access frames of another
+ # interpreter and crash
+ import threading
+
+ entered = threading.Event()
+ left = threading.Event()
+
+ def park():
+ entered.set()
+ left.wait()
+
+ t = threading.Thread(target=park)
+ with threading_helper.start_threads([t], unlock=left.set):
+ entered.wait()
+ interp = interpreters.create()
+ try:
+ interp.exec(f"""if True:
+ import sys
+ import threading
+
+ frames = sys._current_frames()
+ assert threading.get_ident() in frames, frames
+ assert frames[threading.get_ident()].f_globals is globals()
+ assert {t.ident} not in frames, frames
+ """)
+ finally:
+ interp.close()
+
@threading_helper.reap_threads
@threading_helper.requires_working_threading()
def test_current_exceptions(self):
@@ -629,6 +661,39 @@ def g456():
leave_g.set()
t.join()
+ @support.cpython_only
+ @requires_subinterpreters
+ @threading_helper.requires_working_threading()
+ def test_current_exceptions_other_interpreters(self):
+ # gh-158364: sys._current_exceptions() would hand out exceptions of
+ # another interpreter and crash
+ import threading
+
+ entered = threading.Event()
+ left = threading.Event()
+
+ def hold():
+ # The thread has to be handling an exception, otherwise
+ # sys._current_exceptions() has nothing to report for it.
+ try:
+ raise ValueError
+ except ValueError:
+ entered.set()
+ left.wait()
+
+ t = threading.Thread(target=hold)
+ with threading_helper.start_threads([t], unlock=left.set):
+ entered.wait()
+ interp = interpreters.create()
+ try:
+ interp.exec(f"""if True:
+ import sys
+
+ assert {t.ident} not in sys._current_exceptions()
+ """)
+ finally:
+ interp.close()
+
def test_attributes(self):
self.assertIsInstance(sys.api_version, int)
self.assertIsInstance(sys.argv, list)
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst
new file mode 100644
index 00000000000000..14ab4ef12becae
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-28-18-58-27.gh-issue-158364.jBqmTs.rst
@@ -0,0 +1,3 @@
+Fix crash when :func:`sys._current_frames` or
+:func:`sys._current_exceptions` is called while another interpreter is
+running.
diff --git a/Python/pystate.c b/Python/pystate.c
index 737bf0f216bd34..76ad958478a3ca 100644
--- a/Python/pystate.c
+++ b/Python/pystate.c
@@ -2801,36 +2801,33 @@ _PyThread_CurrentFrames(void)
return NULL;
}
- /* for i in all interpreters:
- * for t in all of i's thread states:
- * if t's frame isn't NULL, map t's id to its frame
+ /* for t in all of the current interpreter's thread states:
+ * if t's frame isn't NULL, map t's id to its frame
* Because these lists can mutate even when the GIL is held, we
* need to grab head_mutex for the duration.
*/
- _PyEval_StopTheWorldAll(runtime);
+ PyInterpreterState *interp = tstate->interp;
+ _PyEval_StopTheWorld(interp);
HEAD_LOCK(runtime);
- PyInterpreterState *i;
- for (i = runtime->interpreters.head; i != NULL; i = i->next) {
- _Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
- _PyInterpreterFrame *frame = t->current_frame;
- frame = _PyFrame_GetFirstComplete(frame);
- if (frame == NULL) {
- continue;
- }
- PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
- if (id == NULL) {
- goto fail;
- }
- PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
- if (frameobj == NULL) {
- Py_DECREF(id);
- goto fail;
- }
- int stat = PyDict_SetItem(result, id, frameobj);
+ _Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
+ _PyInterpreterFrame *frame = t->current_frame;
+ frame = _PyFrame_GetFirstComplete(frame);
+ if (frame == NULL) {
+ continue;
+ }
+ PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
+ if (id == NULL) {
+ goto fail;
+ }
+ PyObject *frameobj = (PyObject *)_PyFrame_GetFrameObject(frame);
+ if (frameobj == NULL) {
Py_DECREF(id);
- if (stat < 0) {
- goto fail;
- }
+ goto fail;
+ }
+ int stat = PyDict_SetItem(result, id, frameobj);
+ Py_DECREF(id);
+ if (stat < 0) {
+ goto fail;
}
}
goto done;
@@ -2840,7 +2837,7 @@ _PyThread_CurrentFrames(void)
done:
HEAD_UNLOCK(runtime);
- _PyEval_StartTheWorldAll(runtime);
+ _PyEval_StartTheWorld(interp);
return result;
}
@@ -2866,35 +2863,32 @@ _PyThread_CurrentExceptions(void)
return NULL;
}
- /* for i in all interpreters:
- * for t in all of i's thread states:
- * if t's frame isn't NULL, map t's id to its frame
+ /* for t in all of the current interpreter's thread states:
+ * if t's frame isn't NULL, map t's id to its exception
* Because these lists can mutate even when the GIL is held, we
* need to grab head_mutex for the duration.
*/
- _PyEval_StopTheWorldAll(runtime);
+ PyInterpreterState *interp = tstate->interp;
+ _PyEval_StopTheWorld(interp);
HEAD_LOCK(runtime);
- PyInterpreterState *i;
- for (i = runtime->interpreters.head; i != NULL; i = i->next) {
- _Py_FOR_EACH_TSTATE_UNLOCKED(i, t) {
- _PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
- if (err_info == NULL) {
- continue;
- }
- PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
- if (id == NULL) {
- goto fail;
- }
- PyObject *exc = err_info->exc_value;
- assert(exc == NULL ||
- exc == Py_None ||
- PyExceptionInstance_Check(exc));
-
- int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
- Py_DECREF(id);
- if (stat < 0) {
- goto fail;
- }
+ _Py_FOR_EACH_TSTATE_UNLOCKED(interp, t) {
+ _PyErr_StackItem *err_info = _PyErr_GetTopmostException(t);
+ if (err_info == NULL) {
+ continue;
+ }
+ PyObject *id = PyLong_FromUnsignedLong(t->thread_id);
+ if (id == NULL) {
+ goto fail;
+ }
+ PyObject *exc = err_info->exc_value;
+ assert(exc == NULL ||
+ exc == Py_None ||
+ PyExceptionInstance_Check(exc));
+
+ int stat = PyDict_SetItem(result, id, exc == NULL ? Py_None : exc);
+ Py_DECREF(id);
+ if (stat < 0) {
+ goto fail;
}
}
goto done;
@@ -2904,7 +2898,7 @@ _PyThread_CurrentExceptions(void)
done:
HEAD_UNLOCK(runtime);
- _PyEval_StartTheWorldAll(runtime);
+ _PyEval_StartTheWorld(interp);
return result;
}
_______________________________________________
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]