gh-142975: During GC, mark frozen objects with a merged zero refcount for destruction (GH-143156)

This commit is contained in:
Peter Bierma 2025-12-25 11:31:41 -05:00 committed by GitHub
parent 579c5b496b
commit 8611f74e08
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 39 additions and 1 deletions

View file

@ -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()

View file

@ -0,0 +1,2 @@
Fix crash after unfreezing all objects tracked by the garbage collector on
the :term:`free threaded <free threading>` build.

View file

@ -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);
// gh-142975: 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