EDIT: Added, -1 error return case per @vstinner's suggestion.
EDIT 2: @vstinner added a vote.
I propose adding a dedicated C API function to check if a weak reference is dead:
// Returns 1 if the pointed to object is dead, 0 if it's alive, and -1 with an error set if `ref` is not a weak reference.
int PyWeakref_IsDead(PyObject *ref);
Motivation
Prior to Python 3.13, you could check if a weak reference is dead via PyWeakref_GetObject(ref) == Py_None, but that function is now deprecated. You might try writing an "is dead" check using PyWeakref_GetRef. For example:
int is_dead(PyObject *ref) {
PyObject *tmp;
if (PyWeakref_GetRef(&ref, &tmp) < 0) {
return -1;
}
else if (tmp == NULL) {
return 1;
}
Py_DECREF(tmp);
return 0;
}
In addition to not being ergonomic, the problem with this code is that the Py_DECREF(tmp) may introduce a side effect from a calling a destructor, at least in the free threading build where some other thread may concurrently drop the last reference. Our internal _PyWeakref_IS_DEAD implementation avoids this problem, but it's not possible to reimplement that code using our existing public APIs.
This can be a problem when you need to check if a weak reference is dead within a lock, such as when cleaning up dictionaries or lists of weak references -- you don't want to execute arbitrary code via a destructor while holding the lock.
I've run into this in two C API extensions this week that are not currently thread-safe with free threading:
- CFFI uses a dictionary that maps a string keys to unowned references. I'm working on update it to use
PyWeakReference, but the "is dead" clean-up checks are more difficult due to the above issues.
- Pandas cleans up a list of weak references. (The code is not currently thread-safe, and probably needs a lock.)
Vote
EDIT: Added,
-1error return case per @vstinner's suggestion.EDIT 2: @vstinner added a vote.
I propose adding a dedicated C API function to check if a weak reference is dead:
Motivation
Prior to Python 3.13, you could check if a weak reference is dead via
PyWeakref_GetObject(ref) == Py_None, but that function is now deprecated. You might try writing an "is dead" check usingPyWeakref_GetRef. For example:In addition to not being ergonomic, the problem with this code is that the
Py_DECREF(tmp)may introduce a side effect from a calling a destructor, at least in the free threading build where some other thread may concurrently drop the last reference. Our internal_PyWeakref_IS_DEADimplementation avoids this problem, but it's not possible to reimplement that code using our existing public APIs.This can be a problem when you need to check if a weak reference is dead within a lock, such as when cleaning up dictionaries or lists of weak references -- you don't want to execute arbitrary code via a destructor while holding the lock.
I've run into this in two C API extensions this week that are not currently thread-safe with free threading:
PyWeakReference, but the "is dead" clean-up checks are more difficult due to the above issues.Vote