mirror of
https://github.com/python/cpython.git
synced 2025-07-15 23:35:23 +00:00
[3.13] gh-121794: Don't set ob_tid
to zero in fast-path dealloc (GH-121799) (#121821)
We should maintain the invariant that a zero `ob_tid` implies the
refcount fields are merged.
* Move the assignment in `_Py_MergeZeroLocalRefcount` to immediately
before the refcount merge.
* Update `_PyTrash_thread_destroy_chain` to set `ob_ref_shared` to
`_Py_REF_MERGED` when setting `ob_tid` to zero.
Also check this invariant with assertions in the GC in debug builds.
That uncovered a bug when running out of memory during GC.
(cherry picked from commit d23be3947c
)
Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
parent
6396c77571
commit
0794220a69
3 changed files with 62 additions and 13 deletions
|
@ -455,6 +455,30 @@ mark_reachable(PyObject *op)
|
|||
}
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
static bool
|
||||
validate_refcounts(const mi_heap_t *heap, const mi_heap_area_t *area,
|
||||
void *block, size_t block_size, void *args)
|
||||
{
|
||||
PyObject *op = op_from_block(block, args, false);
|
||||
if (op == NULL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
_PyObject_ASSERT_WITH_MSG(op, !gc_is_unreachable(op),
|
||||
"object should not be marked as unreachable yet");
|
||||
|
||||
if (_Py_REF_IS_MERGED(op->ob_ref_shared)) {
|
||||
_PyObject_ASSERT_WITH_MSG(op, op->ob_tid == 0,
|
||||
"merged objects should have ob_tid == 0");
|
||||
}
|
||||
else if (!_Py_IsImmortal(op)) {
|
||||
_PyObject_ASSERT_WITH_MSG(op, op->ob_tid != 0,
|
||||
"unmerged objects should have ob_tid != 0");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
validate_gc_objects(const mi_heap_t *heap, const mi_heap_area_t *area,
|
||||
void *block, size_t block_size, void *args)
|
||||
|
@ -498,6 +522,19 @@ mark_heap_visitor(const mi_heap_t *heap, const mi_heap_area_t *area,
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
restore_refs(const mi_heap_t *heap, const mi_heap_area_t *area,
|
||||
void *block, size_t block_size, void *args)
|
||||
{
|
||||
PyObject *op = op_from_block(block, args, false);
|
||||
if (op == NULL) {
|
||||
return true;
|
||||
}
|
||||
gc_restore_tid(op);
|
||||
gc_clear_unreachable(op);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Return true if object has a pre-PEP 442 finalization method. */
|
||||
static int
|
||||
has_legacy_finalizer(PyObject *op)
|
||||
|
@ -549,6 +586,13 @@ static int
|
|||
deduce_unreachable_heap(PyInterpreterState *interp,
|
||||
struct collection_state *state)
|
||||
{
|
||||
|
||||
#ifdef GC_DEBUG
|
||||
// Check that all objects are marked as unreachable and that the computed
|
||||
// reference count difference (stored in `ob_tid`) is non-negative.
|
||||
gc_visit_heaps(interp, &validate_refcounts, &state->base);
|
||||
#endif
|
||||
|
||||
// Identify objects that are directly reachable from outside the GC heap
|
||||
// by computing the difference between the refcount and the number of
|
||||
// incoming references.
|
||||
|
@ -563,6 +607,8 @@ deduce_unreachable_heap(PyInterpreterState *interp,
|
|||
// Transitively mark reachable objects by clearing the
|
||||
// _PyGC_BITS_UNREACHABLE flag.
|
||||
if (gc_visit_heaps(interp, &mark_heap_visitor, &state->base) < 0) {
|
||||
// On out-of-memory, restore the refcounts and bail out.
|
||||
gc_visit_heaps(interp, &restore_refs, &state->base);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -1066,7 +1112,8 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
|
|||
int err = deduce_unreachable_heap(interp, state);
|
||||
if (err < 0) {
|
||||
_PyEval_StartTheWorld(interp);
|
||||
goto error;
|
||||
PyErr_NoMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
// Print debugging information.
|
||||
|
@ -1100,7 +1147,12 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
|
|||
_PyEval_StartTheWorld(interp);
|
||||
|
||||
if (err < 0) {
|
||||
goto error;
|
||||
cleanup_worklist(&state->unreachable);
|
||||
cleanup_worklist(&state->legacy_finalizers);
|
||||
cleanup_worklist(&state->wrcb_to_call);
|
||||
cleanup_worklist(&state->objs_to_decref);
|
||||
PyErr_NoMemory();
|
||||
return;
|
||||
}
|
||||
|
||||
// Call tp_clear on objects in the unreachable set. This will cause
|
||||
|
@ -1110,15 +1162,6 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
|
|||
|
||||
// Append objects with legacy finalizers to the "gc.garbage" list.
|
||||
handle_legacy_finalizers(state);
|
||||
return;
|
||||
|
||||
error:
|
||||
cleanup_worklist(&state->unreachable);
|
||||
cleanup_worklist(&state->legacy_finalizers);
|
||||
cleanup_worklist(&state->wrcb_to_call);
|
||||
cleanup_worklist(&state->objs_to_decref);
|
||||
PyErr_NoMemory();
|
||||
PyErr_FormatUnraisable("Out of memory during garbage collection");
|
||||
}
|
||||
|
||||
/* This is the main function. Read this to understand how the
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue