Skip to content

Commit 8611f74

Browse files
gh-142975: During GC, mark frozen objects with a merged zero refcount for destruction (GH-143156)
1 parent 579c5b4 commit 8611f74

File tree

3 files changed

+39
-1
lines changed

3 files changed

+39
-1
lines changed

Lib/test/test_free_threading/test_gc.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,38 @@ def mutator_thread():
6262
with threading_helper.start_threads(gcs + mutators):
6363
pass
6464

65+
def test_freeze_object_in_brc_queue(self):
66+
# GH-142975: Freezing objects in the BRC queue could result in some
67+
# objects having a zero refcount without being deallocated.
68+
69+
class Weird:
70+
# We need a destructor to trigger the check for object resurrection
71+
def __del__(self):
72+
pass
73+
74+
# This is owned by the main thread, so the subthread will have to increment
75+
# this object's reference count.
76+
weird = Weird()
77+
78+
def evil():
79+
gc.freeze()
80+
81+
# Decrement the reference count from this thread, which will trigger the
82+
# slow path during resurrection and add our weird object to the BRC queue.
83+
nonlocal weird
84+
del weird
85+
86+
# Collection will merge the object's reference count and make it zero.
87+
gc.collect()
88+
89+
# Unfreeze the object, making it visible to the GC.
90+
gc.unfreeze()
91+
gc.collect()
92+
93+
thread = Thread(target=evil)
94+
thread.start()
95+
thread.join()
96+
6597

6698
if __name__ == "__main__":
6799
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix crash after unfreezing all objects tracked by the garbage collector on
2+
the :term:`free threaded <free threading>` build.

Python/gc_free_threading.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -906,7 +906,11 @@ gc_visit_thread_stacks_mark_alive(PyInterpreterState *interp, gc_mark_args_t *ar
906906
static void
907907
queue_untracked_obj_decref(PyObject *op, struct collection_state *state)
908908
{
909-
if (!_PyObject_GC_IS_TRACKED(op)) {
909+
assert(Py_REFCNT(op) == 0);
910+
// gh-142975: We have to treat frozen objects as untracked in this function
911+
// or else they might be picked up in a future collection, which breaks the
912+
// assumption that all incoming objects have a non-zero reference count.
913+
if (!_PyObject_GC_IS_TRACKED(op) || gc_is_frozen(op)) {
910914
// GC objects with zero refcount are handled subsequently by the
911915
// GC as if they were cyclic trash, but we have to handle dead
912916
// non-GC objects here. Add one to the refcount so that we can

0 commit comments

Comments
 (0)