diff --git a/Python/gc_free_threading.c b/Python/gc_free_threading.c index 51261cea0cfe2c..beb3fa588f40e7 100644 --- a/Python/gc_free_threading.c +++ b/Python/gc_free_threading.c @@ -308,17 +308,18 @@ disable_deferred_refcounting(PyObject *op) // should also be disabled when we turn off deferred refcounting. _PyObject_DisablePerThreadRefcounting(op); } - - // Generators and frame objects may contain deferred references to other - // objects. If the pointed-to objects are part of cyclic trash, we may - // have disabled deferred refcounting on them and need to ensure that we - // use strong references, in case the generator or frame object is - // resurrected by a finalizer. - if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { - frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe); - } - else if (PyFrame_Check(op)) { - frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame); + if (_PyObject_GC_IS_TRACKED(op)) { + // Generators and frame objects may contain deferred references to other + // objects. If the pointed-to objects are part of cyclic trash, we may + // have disabled deferred refcounting on them and need to ensure that we + // use strong references, in case the generator or frame object is + // resurrected by a finalizer. + if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) { + frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe); + } + else if (PyFrame_Check(op)) { + frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame); + } } } @@ -1240,19 +1241,30 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area, return true; } - if (state->reason == _Py_GC_REASON_SHUTDOWN) { - // Disable deferred refcounting for reachable objects as well during - // interpreter shutdown. This ensures that these objects are collected - // immediately when their last reference is removed. - disable_deferred_refcounting(op); - } - // object is reachable, restore `ob_tid`; we're done with these objects gc_restore_tid(op); gc_clear_alive(op); return true; } +// Disable deferred refcounting for reachable objects during interpreter +// shutdown. This ensures that these objects are collected immediately when +// their last reference is removed. This needs to consider both tracked and +// untracked GC objects, since either might have deferred refcounts enabled. +static bool +scan_heap_disable_deferred(const mi_heap_t *heap, const mi_heap_area_t *area, + void *block, size_t block_size, void *args) +{ + PyObject *op = op_from_block_all_gc(block, args); + if (op == NULL) { + return true; + } + if (!_Py_IsImmortal(op)) { + disable_deferred_refcounting(op); + } + return true; +} + static int move_legacy_finalizer_reachable(struct collection_state *state); @@ -1487,6 +1499,10 @@ deduce_unreachable_heap(PyInterpreterState *interp, // Restores ob_tid for reachable objects. gc_visit_heaps(interp, &scan_heap_visitor, &state->base); + if (state->reason == _Py_GC_REASON_SHUTDOWN) { + gc_visit_heaps(interp, &scan_heap_disable_deferred, &state->base); + } + if (state->legacy_finalizers.head) { // There may be objects reachable from legacy finalizers that are in // the unreachable set. We need to mark them as reachable.