Skip to content

Commit 2500eb9

Browse files
authored
gh-135909: Assert incoming refcnt != 0 for the free threaded GC (GH-136009)
This helps catch double deallocation bugs and is similar to the assertion in the GIL-enabled build. The call to `validate_refcounts` is moved up to start of the GC because `queue_untracked_obj_decref()` creates it own zero reference count garbage.
1 parent be02e68 commit 2500eb9

File tree

1 file changed

+15
-7
lines changed

1 file changed

+15
-7
lines changed

Python/gc_free_threading.c

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,6 +1073,14 @@ validate_refcounts(const mi_heap_t *heap, const mi_heap_area_t *area,
10731073
return true;
10741074
}
10751075

1076+
// This assert mirrors the one in Python/gc.c:update_refs(). There must be
1077+
// no tracked objects with a reference count of 0 when the cyclic
1078+
// collector starts. If there is, then the collector will double dealloc
1079+
// the object. The likely cause for hitting this is a faulty .tp_dealloc.
1080+
// Also see the comment in `update_refs()`.
1081+
_PyObject_ASSERT_WITH_MSG(op, Py_REFCNT(op) > 0,
1082+
"tracked objects must have a reference count > 0");
1083+
10761084
_PyObject_ASSERT_WITH_MSG(op, !gc_is_unreachable(op),
10771085
"object should not be marked as unreachable yet");
10781086

@@ -1422,13 +1430,6 @@ static int
14221430
deduce_unreachable_heap(PyInterpreterState *interp,
14231431
struct collection_state *state)
14241432
{
1425-
1426-
#ifdef GC_DEBUG
1427-
// Check that all objects are marked as unreachable and that the computed
1428-
// reference count difference (stored in `ob_tid`) is non-negative.
1429-
gc_visit_heaps(interp, &validate_refcounts, &state->base);
1430-
#endif
1431-
14321433
// Identify objects that are directly reachable from outside the GC heap
14331434
// by computing the difference between the refcount and the number of
14341435
// incoming references.
@@ -2158,6 +2159,13 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
21582159
state->gcstate->old[i-1].count = 0;
21592160
}
21602161

2162+
#ifdef GC_DEBUG
2163+
// Before we start, check that the heap is in a good condition. There must
2164+
// be no objects with a zero reference count. And `ob_tid` must only have a
2165+
// thread if the refcount is unmerged.
2166+
gc_visit_heaps(interp, &validate_refcounts, &state->base);
2167+
#endif
2168+
21612169
_Py_FOR_EACH_TSTATE_BEGIN(interp, p) {
21622170
_PyThreadStateImpl *tstate = (_PyThreadStateImpl *)p;
21632171

0 commit comments

Comments
 (0)