Skip to content

Commit 795d5c5

Browse files
authored
gh-144054: shutdown fix for deferred ref counting (GH-144055)
When shutting down, disable deferred refcounting for all GC objects. It is important to do this also for untracked objects, which before this change were getting missed. Small code cleanup: We can remove the shutdown case disable_deferred_refcounting() call inside scan_heap_visitor() if we are careful about it. The key is that frame_disable_deferred_refcounting() might fail if the object is untracked.
1 parent 31c81ab commit 795d5c5

File tree

1 file changed

+34
-18
lines changed

1 file changed

+34
-18
lines changed

Python/gc_free_threading.c

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -308,17 +308,18 @@ disable_deferred_refcounting(PyObject *op)
308308
// should also be disabled when we turn off deferred refcounting.
309309
_PyObject_DisablePerThreadRefcounting(op);
310310
}
311-
312-
// Generators and frame objects may contain deferred references to other
313-
// objects. If the pointed-to objects are part of cyclic trash, we may
314-
// have disabled deferred refcounting on them and need to ensure that we
315-
// use strong references, in case the generator or frame object is
316-
// resurrected by a finalizer.
317-
if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) {
318-
frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe);
319-
}
320-
else if (PyFrame_Check(op)) {
321-
frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame);
311+
if (_PyObject_GC_IS_TRACKED(op)) {
312+
// Generators and frame objects may contain deferred references to other
313+
// objects. If the pointed-to objects are part of cyclic trash, we may
314+
// have disabled deferred refcounting on them and need to ensure that we
315+
// use strong references, in case the generator or frame object is
316+
// resurrected by a finalizer.
317+
if (PyGen_CheckExact(op) || PyCoro_CheckExact(op) || PyAsyncGen_CheckExact(op)) {
318+
frame_disable_deferred_refcounting(&((PyGenObject *)op)->gi_iframe);
319+
}
320+
else if (PyFrame_Check(op)) {
321+
frame_disable_deferred_refcounting(((PyFrameObject *)op)->f_frame);
322+
}
322323
}
323324
}
324325

@@ -1240,19 +1241,30 @@ scan_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
12401241
return true;
12411242
}
12421243

1243-
if (state->reason == _Py_GC_REASON_SHUTDOWN) {
1244-
// Disable deferred refcounting for reachable objects as well during
1245-
// interpreter shutdown. This ensures that these objects are collected
1246-
// immediately when their last reference is removed.
1247-
disable_deferred_refcounting(op);
1248-
}
1249-
12501244
// object is reachable, restore `ob_tid`; we're done with these objects
12511245
gc_restore_tid(op);
12521246
gc_clear_alive(op);
12531247
return true;
12541248
}
12551249

1250+
// Disable deferred refcounting for reachable objects during interpreter
1251+
// shutdown. This ensures that these objects are collected immediately when
1252+
// their last reference is removed. This needs to consider both tracked and
1253+
// untracked GC objects, since either might have deferred refcounts enabled.
1254+
static bool
1255+
scan_heap_disable_deferred(const mi_heap_t *heap, const mi_heap_area_t *area,
1256+
void *block, size_t block_size, void *args)
1257+
{
1258+
PyObject *op = op_from_block_all_gc(block, args);
1259+
if (op == NULL) {
1260+
return true;
1261+
}
1262+
if (!_Py_IsImmortal(op)) {
1263+
disable_deferred_refcounting(op);
1264+
}
1265+
return true;
1266+
}
1267+
12561268
static int
12571269
move_legacy_finalizer_reachable(struct collection_state *state);
12581270

@@ -1487,6 +1499,10 @@ deduce_unreachable_heap(PyInterpreterState *interp,
14871499
// Restores ob_tid for reachable objects.
14881500
gc_visit_heaps(interp, &scan_heap_visitor, &state->base);
14891501

1502+
if (state->reason == _Py_GC_REASON_SHUTDOWN) {
1503+
gc_visit_heaps(interp, &scan_heap_disable_deferred, &state->base);
1504+
}
1505+
14901506
if (state->legacy_finalizers.head) {
14911507
// There may be objects reachable from legacy finalizers that are in
14921508
// the unreachable set. We need to mark them as reachable.

0 commit comments

Comments
 (0)