Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

_Py_MergeZeroLocalRefcount should not setob_tid to zero in fast-path dealloc #121794

Closed
Labels
3.13bugs and security fixes3.14bugs and security fixestopic-free-threadingtype-bugAn unexpected behavior, bug, or error
@colesbury

Description

@colesbury

Bug report

The_Py_MergeZeroLocalRefcount function is called when the local refcount field reaches zero. We generally maintain the invariant1 thatob_tid == 0 implies that the refcount fields are merged (i.e.,ob_ref_shared flags are_Py_REF_MERGED) and vice versa.

The current implementation breaks the invariant by settingob_tid to zero when the refcount fields aren't merged. Typically, this isn't a problem because:

  • Most commonly, the object is deallocated so the values do not matter
  • If the object is resurrected insubtype_dealloc (e.g., for a finalizer), we usePy_SET_REFCNT, which will mark the fields as merged, restoring the invariant

However, if resurrection is done slightly differently, such as byPy_INCREF(), then things can break in very strange ways:

  • The GC may restoreob_tid from the allocator (because it's not merged), butob_ref_local is still zero. The nextPy_DECREF then leads to a "negative refcount" error.

Summary

We should maintain the invariant thatob_tid == 0 <=>_Py_REF_IS_MERGED() and check it with assertions when possible.

void
_Py_MergeZeroLocalRefcount(PyObject*op)
{
assert(op->ob_ref_local==0);
_Py_atomic_store_uintptr_relaxed(&op->ob_tid,0);
Py_ssize_tshared=_Py_atomic_load_ssize_acquire(&op->ob_ref_shared);
if (shared==0) {
// Fast-path: shared refcount is zero (including flags)
_Py_Dealloc(op);
return;
}
// Slow-path: atomically set the flags (low two bits) to _Py_REF_MERGED.
Py_ssize_tnew_shared;
do {
new_shared= (shared& ~_Py_REF_SHARED_FLAG_MASK) |_Py_REF_MERGED;
}while (!_Py_atomic_compare_exchange_ssize(&op->ob_ref_shared,
&shared,new_shared));
if (new_shared==_Py_REF_MERGED) {
// i.e., the shared refcount is zero (only the flags are set) so we
// deallocate the object.
_Py_Dealloc(op);
}
}

Originally reported by@albanD

Linked PRs

Footnotes

  1. There are a few places where we deliberately re-useob_tid for other purposes, such as the trashcan mechanism and during GC, but these objects are not visible to other parts of the program.

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.13bugs and security fixes3.14bugs and security fixestopic-free-threadingtype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp