-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make _Py_TryIncref
public as an unstable API as PyUnstable_Object_TryIncref()
#128844
Comments
Since Py_INCREF() and Py_NewRef() omit "PyObject" in their name, I suggest
Would you mind to elaborate this function? What is its API? What is its usage? |
One thing is not clear to me. I suspect there's a mechanism to prevent that in free-threaded builds, but I'd like to know what it is and how it applies. |
When called on an object, The API is a bit of a "wart", but I don't see a way to avoid it. // Enable subsequent calls to `PyUnstable_TryIncref` on `op`
void PyUnstable_Object_EnableTryIncRef(PyObject *op);
In the asyncio and pybind11 use cases, there are already locks during deallocation and around the Here's an example in psuedo-code based on pybind11's use case. Note that when The // map from C++ pointers to Python wrapper objects
std::unordered_map<void *, PyObject *> registered_instances;
std::mutex mutex;
// Find an existing Python wrapper for a C++ pointer
PyObject *
get_wrapper(void *ptr)
{
std::unique_lock<std::mutex> lk(mutex);
if (auto it = registered_instances.find(ptr); it != registered_instances.end()) {
PyObject *wrapper = it->second;
if (_Py_TryIncref(wrapper)) { // NOTE: `Py_INCREF()` here would not be safe in the free threading build!
return wrapper;
}
}
}
void
wrapper_dealloc(PyObject *wrapper)
{
std::unique_lock<std::mutex> lk(mutex);
void *ptr = get_cpp_ptr_from_wrapper(wrapper);
registered_instances.erase(ptr);
...
} |
Ok, I'm fine with adding these two PyUnstable functions. |
Feature or enhancement
We should make
_Py_TryIncref
public as function with the following signature:The function increments the reference count if it's not zero in a thread-safe way. It's logically equivalent to the following snippet and in the default (GIL-enabled) build it's implemented as such:
Additionally, we should make
_PyObject_SetMaybeWeakref
public asPyUnstable_Object_EnableTryIncRef
. This function has no equivalent in the GIL-enabled build (it's a no-op), but it's important for makingTryIncref
work reliably with our biased reference counting implementation.Motivation
The
TryIncref
primitive is a building block for handling borrowed and unowned references. It addresses an issue that generally cannot be solved by adding extra synchronization like mutexes because it handles the race between the reference count reaching zero (which is outside developers' control) and theTryIncref
.We use it internally in three subsystems:
PyObject *
entries.Recently, we discovered a thread safety bug in pybind11 related to the use of borrowed/unowned references. Using
_Py_TryIncref
in place ofPy_INCREF
would fix the bug. I think nanobind probably has a similar issue.Alternatives
PyWeakRef
objects increases the overhead of pybind11 bindings by 30% in some simple tests._Py_TryIncref
in extensions. I think this is much worse than making the function public as an unstable API because it requires direct access to the reference count fields -- the implementation is tied to the implementation of biased reference counting -- and I'd like to avoid extensions depending directly on those details.See also
Py_INCREF
for the free-threaded build #113920The text was updated successfully, but these errors were encountered: