mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
[3.13] gh-127582: Make object resurrection thread-safe for free threading. (GH-127612) (GH-127659)
Objects may be temporarily "resurrected" in destructors when calling
finalizers or watcher callbacks. We previously undid the resurrection
by decrementing the reference count using `Py_SET_REFCNT`. This was not
thread-safe because other threads might be accessing the object
(modifying its reference count) if it was exposed by the finalizer,
watcher callback, or temporarily accessed by a racy dictionary or list
access.
This adds internal-only thread-safe functions for temporary object
resurrection during destructors.
(cherry picked from commit f4f530804b
)
This commit is contained in:
parent
304111e967
commit
69bb1c6c41
6 changed files with 87 additions and 20 deletions
|
@ -572,8 +572,52 @@ _PyObject_SetMaybeWeakref(PyObject *op)
|
|||
}
|
||||
}
|
||||
|
||||
extern int _PyObject_ResurrectEndSlow(PyObject *op);
|
||||
#endif
|
||||
|
||||
// Temporarily resurrects an object during deallocation. The refcount is set
|
||||
// to one.
|
||||
static inline void
|
||||
_PyObject_ResurrectStart(PyObject *op)
|
||||
{
|
||||
assert(Py_REFCNT(op) == 0);
|
||||
#ifdef Py_REF_DEBUG
|
||||
_Py_IncRefTotal(_PyThreadState_GET());
|
||||
#endif
|
||||
#ifdef Py_GIL_DISABLED
|
||||
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, _Py_ThreadId());
|
||||
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 1);
|
||||
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, 0);
|
||||
#else
|
||||
Py_SET_REFCNT(op, 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Undoes an object resurrection by decrementing the refcount without calling
|
||||
// _Py_Dealloc(). Returns 0 if the object is dead (the normal case), and
|
||||
// deallocation should continue. Returns 1 if the object is still alive.
|
||||
static inline int
|
||||
_PyObject_ResurrectEnd(PyObject *op)
|
||||
{
|
||||
#ifdef Py_REF_DEBUG
|
||||
_Py_DecRefTotal(_PyThreadState_GET());
|
||||
#endif
|
||||
#ifndef Py_GIL_DISABLED
|
||||
Py_SET_REFCNT(op, Py_REFCNT(op) - 1);
|
||||
return Py_REFCNT(op) != 0;
|
||||
#else
|
||||
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
|
||||
Py_ssize_t shared = _Py_atomic_load_ssize_acquire(&op->ob_ref_shared);
|
||||
if (_Py_IsOwnedByCurrentThread(op) && local == 1 && shared == 0) {
|
||||
// Fast-path: object has a single refcount and is owned by this thread
|
||||
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0);
|
||||
return 0;
|
||||
}
|
||||
// Slow-path: object has a shared refcount or is not owned by this thread
|
||||
return _PyObject_ResurrectEndSlow(op);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Tries to incref op and returns 1 if successful or 0 otherwise. */
|
||||
static inline int
|
||||
_Py_TryIncref(PyObject *op)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue