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
32 changes: 32 additions & 0 deletions Lib/test/test_free_threading/test_gc.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,38 @@ def mutator_thread():
with threading_helper.start_threads(gcs + mutators):
pass

def test_freeze_object_in_brc_queue(self):
# GH-142975: Freezing objects in the BRC queue could result in some
# objects having a zero refcount without being deallocated.

class Weird:
# We need a destructor to trigger the check for object resurrection
def __del__(self):
pass

# This is owned by the main thread, so the subthread will have to increment
# this object's reference count.
weird = Weird()

def evil():
gc.freeze()

# Decrement the reference count from this thread, which will trigger the
# slow path during resurrection and add our weird object to the BRC queue.
nonlocal weird
del weird

# Collection will merge the object's reference count and make it zero.
gc.collect()

# Unfreeze the object, making it visible to the GC.
gc.unfreeze()
gc.collect()

thread = Thread(target=evil)
thread.start()
thread.join()


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix crash after unfreezing all objects tracked by the garbage collector on
the :term:`free threaded <free threading>` build.
6 changes: 5 additions & 1 deletion Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,11 @@ gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, gc_mark_args_t *ar
static void
queue_untracked_obj_decref(PyObject *op, struct collection_state *state)
{
if (!_PyObject_GC_IS_TRACKED(op)) {
assert(Py_REFCNT(op) == 0);
// We have to treat frozen objects as untracked in this function or else
// they might be picked up in a future collection, which breaks the assumption
// that all incoming objects have a non-zero reference count.
if (!_PyObject_GC_IS_TRACKED(op) || gc_is_frozen(op)) {
// GC objects with zero refcount are handled subsequently by the
// GC as if they were cyclic trash, but we have to handle dead
// non-GC objects here. Add one to the refcount so that we can
Expand Down
Loading