https://github.com/python/cpython/commit/6e9a900916e32f4565f0ecebf7b6f48f5a60bc66
commit: 6e9a900916e32f4565f0ecebf7b6f48f5a60bc66
branch: main
author: John <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-11-09T18:54:57+05:30
summary:
gh-161366: Fix deadlock with FT GC debug collectables print (#161444)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-21-00-57.gh-issue-161366.ouxAvv.rst
M Lib/test/test_free_threading/test_gc.py
M Python/gc_free_threading.c
diff --git a/Lib/test/test_free_threading/test_gc.py
b/Lib/test/test_free_threading/test_gc.py
index b287b51826999c..8c97b04ed4f71b 100644
--- a/Lib/test/test_free_threading/test_gc.py
+++ b/Lib/test/test_free_threading/test_gc.py
@@ -9,6 +9,7 @@
from test import support
from test.support import threading_helper
+from test.support.script_helper import assert_python_ok
class MyObj:
@@ -110,6 +111,35 @@ def run(self):
self.assertTrue(C().run())
+ def test_debug_collectable_python_stderr(self):
+ # The DEBUG_COLLECTABLE output is written with sys.stderr.write(),
+ # which may run Python code or wait for another thread (e.g. for the
+ # lock of a buffered stream), so it must not be written while the
+ # world is stopped. Debug builds assert that the world is not stopped
+ # when Python code runs, so use a sys.stderr written in Python.
+ code = """if 1:
+ import gc
+ import io
+ import sys
+
+ class Stderr(io.StringIO):
+ def write(self, s):
+ return super().write(s)
+
+ sys.stderr = Stderr()
+ gc.set_debug(gc.DEBUG_COLLECTABLE)
+ a = []
+ a.append(a)
+ del a
+ gc.collect()
+ gc.set_debug(0)
+ output = sys.stderr.getvalue()
+ sys.stderr = sys.__stderr__
+ print(output)
+ """
+ _, out, _ = assert_python_ok("-c", code)
+ self.assertRegex(out, b"gc: collectable <list 0x[0-9A-Fa-f]+>")
+
def test_merge_brc_queue_of_detached_thread(self):
# GH-157838: objects queued for merging by a thread that is detached
# (blocked in a lock acquire, sleep, etc.) are merged and freed on its
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-21-00-57.gh-issue-161366.ouxAvv.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-21-00-57.gh-issue-161366.ouxAvv.rst
new file mode 100644
index 00000000000000..94098a0cf5195c
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-11-05-21-00-57.gh-issue-161366.ouxAvv.rst
@@ -0,0 +1,4 @@
+Fix a deadlock in the garbage collector of the :term:`free-threaded build`
+when :data:`gc.DEBUG_COLLECTABLE` is set and another thread is writing to
+:data:`sys.stderr`. The debug output is now written after the other threads
+are resumed.
diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c
index 6eaeeea8848cdb..f4f1a7777f3522 100644
--- a/Python/gc_free_threading.c
+++ b/Python/gc_free_threading.c
@@ -2158,20 +2158,24 @@ gc_collect_internal(PyInterpreterState *interp, struct
collection_state *state,
// Deallocations after the refcount merge may run Python code.
bool needs_resurrection_check = state->objs_to_decref.head != 0;
- // Print debugging information.
- if (FT_ATOMIC_LOAD_INT_RELAXED(interp->gc.debug) &
_PyGC_DEBUG_COLLECTABLE) {
- needs_resurrection_check = true;
- PyObject *op;
- WORKSTACK_FOR_EACH(&state->unreachable, op) {
- debug_cycle("collectable", op);
- }
- }
+ // The debug output is written with sys.stderr.write(), which may run
+ // Python code, so it is printed below after the world is started.
+ bool debug_print = (FT_ATOMIC_LOAD_INT_RELAXED(interp->gc.debug) &
+ _PyGC_DEBUG_COLLECTABLE) != 0;
// Find weakref callbacks and pending finalizers (but do not call them).
bool has_finalizers = prepare_finalization(state,
&needs_resurrection_check);
- if (needs_resurrection_check) {
+ if (needs_resurrection_check || debug_print) {
_PyEval_StartTheWorld(interp);
+ // Print debugging information.
+ if (debug_print) {
+ PyObject *op;
+ WORKSTACK_FOR_EACH(&state->unreachable, op) {
+ debug_cycle("collectable", op);
+ }
+ }
+
// Deallocate any object from the refcount merge step
cleanup_worklist(&state->objs_to_decref);
_______________________________________________
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]