Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions Lib/test/test_free_threading/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -118,6 +119,35 @@ def mutate():
b.wait()
time.sleep(0.2)

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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 9 additions & 5 deletions Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -2137,18 +2137,22 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
gc_visit_heaps(interp, &validate_alive_bits, &state->base);
#endif

// 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 = (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) != 0;

// Find weakref callbacks we will honor (but do not call them).
find_weakref_callbacks(state);
_PyEval_StartTheWorld(interp);

// Print debugging information.
if (interp->gc.debug & _PyGC_DEBUG_COLLECTABLE) {
if (debug_print) {
PyObject *op;
WORKSTACK_FOR_EACH(&state->unreachable, op) {
debug_cycle("collectable", op);
}
}

// Find weakref callbacks we will honor (but do not call them).
find_weakref_callbacks(state);
_PyEval_StartTheWorld(interp);

// Deallocate any object from the refcount merge step
cleanup_worklist(&state->objs_to_decref);

Expand Down
Loading