Skip to content
Open
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
52 changes: 34 additions & 18 deletions Python/gc_free_threading.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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.
Expand Down
Loading