Skip to content
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

C API: Add PyWeakref_GetRef() function #105927

Closed
vstinner opened this issue Jun 19, 2023 · 17 comments
Closed

C API: Add PyWeakref_GetRef() function #105927

vstinner opened this issue Jun 19, 2023 · 17 comments
Labels
topic-C-API type-bug An unexpected behavior, bug, or error

Comments

@vstinner
Copy link
Member

vstinner commented Jun 19, 2023

PyWeakref_GET_OBJECT() and PyWeakref_GetObject() return a borrowed reference to the object, or a borrowed reference to None if the object has been finalized. This API is error-prone and not easy to use.

This API was discussed in 2016: https://mail.python.org/archives/list/python-dev@python.org/thread/6BIPI4MCMAZFLJNIZB4JLTBW2COCACQQ/

I propose adding a new PyWeakref_GetRef() C API function which returns a new strong reference to the object, or NULL if the object has been finalized.

Linked PRs

@vstinner vstinner added type-bug An unexpected behavior, bug, or error topic-C-API labels Jun 19, 2023
@erlend-aasland
Copy link
Contributor

[...] or NULL if the object has been finalized.

With an error set? If no, would you need to call PyErr_Occurred() to find out if an error has occurred? See also capi-workgroup/problems#1

vstinner added a commit to vstinner/cpython that referenced this issue Jun 19, 2023
proxy_checkref() now checks the object, not the proxy.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 19, 2023
* proxy_checkref() now checks the object, not the proxy.
* Most functions take PyObject* instead of PyWeakReference*.
* Remove redundant calls to PyWeakref_GET_OBJECT().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 19, 2023
* proxy_checkref() now checks the object, not the proxy.
* Most functions take PyObject* instead of PyWeakReference*.
* Remove redundant calls to PyWeakref_GET_OBJECT().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 19, 2023
* Rename proxy_checkref() to proxy_check_ref().
* proxy_check_ref() now checks the object, not the proxy.
* Most functions take PyObject* instead of PyWeakReference*.
* Remove redundant calls to PyWeakref_GET_OBJECT().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 19, 2023
* Rename proxy_checkref() to proxy_check_ref().
* proxy_check_ref() now checks the object, not the proxy.
* Most functions take PyObject* instead of PyWeakReference*.
* Remove redundant calls to PyWeakref_GET_OBJECT().
vstinner added a commit that referenced this issue Jun 19, 2023
* Rename proxy_checkref() to proxy_check_ref().
* proxy_check_ref() now checks the object, not the proxy.
* Most functions take PyObject* instead of PyWeakReference*.
* Remove redundant calls to PyWeakref_GET_OBJECT().
vstinner added a commit to vstinner/cpython that referenced this issue Jun 20, 2023
Add new pycore_weakref.h internal header file.
@vstinner
Copy link
Member Author

With an error set? If no, would you need to call PyErr_Occurred() to find out if an error has occurred? See also capi-workgroup/problems#1

No error is set. See PR #105929 for the first part of the implementation.

vstinner added a commit that referenced this issue Jun 20, 2023
Add new pycore_weakref.h internal header file.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 20, 2023
vstinner added a commit to vstinner/cpython that referenced this issue Jun 20, 2023
@erlend-aasland
Copy link
Contributor

No error is set. See PR #105929 for the first part of the implementation.

Would it be better to return None if the object has been finalized? The idiom is that APIs returning a PyObject pointer only will return NULL in case of error.

@vstinner
Copy link
Member Author

Would it be better to return None if the object has been finalized? The idiom is that APIs returning a PyObject pointer only will return NULL in case of error.

It's possible to return a strong reference to None if the reference is dead. But it makes the API harder to handle since you have to handle Py_None reference count in the "dead reference" code path.

In Python 3.12, Py_None is immortal. But I plan to add PyWeakref_GetRef() to https://pythoncapi-compat.readthedocs.io/ and on Python 3.11 and older, you have to carefully Py_DECREF(Py_None).

vstinner added a commit to vstinner/cpython that referenced this issue Jun 20, 2023
@encukou
Copy link
Member

encukou commented Jun 20, 2023

Consider making this int PyWeakref_GetRef(PyObject *weakref, PyObject **value), returning -1 on error (with exception set), 0 on dead reference, and 1 for success (with value set).
See capi-workgroup/problems#1 and especially #105201 (comment) for discussion.

@vstinner
Copy link
Member Author

The proposed function cannot fail. If you use it on the wrong type, it does crash instead.

@encukou
Copy link
Member

encukou commented Jun 20, 2023

I see. Different reasoning then.
Please don't add infallible functions to the stable ABI. There is a possiblity that it will need to fail in the future.

@erlend-aasland
Copy link
Contributor

I agree with Petr; if the internals of the API changes in the future, it would be unfortunate if we also had to add a PyWeakRef_GetRefWithError API as well. However, we take this possibility in account, when designing new APIs.

@erlend-aasland
Copy link
Contributor

See also capi-workgroup/problems#20

@vstinner
Copy link
Member Author

I'm trying to fix race conditions by avoiding borrow references. If the API is modified so it can raise an exeption, it means that all callers must be updated to handle exceptions? That sounds like a lot of work.

I don't see the point of making the API so it can raise an exeption, if there is no known case where it can happen.

I would prefer to not modify PyWeakref_GetRef() to emit a cute exception if the argument is not a weak reference. I prefer to move this responsibility to the caller. The Modules/_abc.c code is an interesting example: it handles the rare case if an object which is not a weakref landed in the container.

I would prefer to have an API simple to use when the caller controls the argument object type.

@encukou
Copy link
Member

encukou commented Jun 20, 2023

I'm trying to fix race conditions by avoiding borrow references. If the API is modified so it can raise an exeption, it means that all callers must be updated to handle exceptions? That sounds like a lot of work.

Yes.

I would prefer to have an API simple to use when the caller controls the argument object type.

Please don't add it to the stable ABI. Maintaining that for decades is also a lot of work, and avoiding the work now means we'll pay in the future.

I would prefer to not modify PyWeakref_GetRef() to emit a cute exception if the argument is not a weak reference.

That's not necessary.
I'm asking for it to be possible to add an error case in the future. For example, on an implementation that uses tagged pointers, you could get a MemoryError when getting the object.

vstinner added a commit to vstinner/cpython that referenced this issue Jun 20, 2023
* Add tests on PyWeakref_NewRef(), PyWeakref_GetObject(),
  PyWeakref_GET_OBJECT() and PyWeakref_GetRef().
* PyWeakref_GetObject() now raises a TypeError if the argument is not
  a weak reference, instead of SystemError.
@vstinner
Copy link
Member Author

Ok, Erlend and Petr convinced me, I updated my PR to use the API:

int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)

vstinner added a commit to vstinner/cpython that referenced this issue Jun 22, 2023
* Replace PyWeakref_GET_OBJECT() with _PyWeakref_GET_REF().
* _sqlite/blob.c now holds a strong reference to the blob object
  while calling close_blob().
* _xidregistry_find_type() now holds a strong reference to registered
  while using it.
vstinner added a commit that referenced this issue Jun 22, 2023
* Add _PyWeakref_IS_DEAD() internal function.
* Modify is_dead_weakref() of Modules/_weakref.c and
  _pysqlite_drop_unused_cursor_references() to replace
  PyWeakref_GET_OBJECT() with _PyWeakref_IS_DEAD().
* Replace "int i" with "Py_ssize_t i" to iterate on cursors
  in _pysqlite_drop_unused_cursor_references().
vstinner added a commit that referenced this issue Jun 22, 2023
* Replace PyWeakref_GET_OBJECT() with _PyWeakref_GET_REF().
* _sqlite/blob.c now holds a strong reference to the blob object
  while calling close_blob().
* _xidregistry_find_type() now holds a strong reference to registered
  while using it.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 22, 2023
It now raises an exception if sys.modules doesn't hold a strong
reference to the module.

Elaborate the comment explaining why a weak reference is used to
create a borrowed reference.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 22, 2023
It now raises an exception if sys.modules doesn't hold a strong
reference to the module.

Elaborate the comment explaining why a weak reference is used to
create a borrowed reference.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 22, 2023
It now raises an exception if sys.modules doesn't hold a strong
reference to the module.

Elaborate the comment explaining why a weak reference is used to
create a borrowed reference.
vstinner added a commit that referenced this issue Jun 22, 2023
It now raises an exception if sys.modules doesn't hold a strong
reference to the module.

Elaborate the comment explaining why a weak reference is used to
create a borrowed reference.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 22, 2023
vstinner added a commit to vstinner/cpython that referenced this issue Jun 23, 2023
Deprecate PyWeakref_GetObject() and PyWeakref_GET_OBJECT() functions.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 23, 2023
Deprecate PyWeakref_GetObject() and PyWeakref_GET_OBJECT() functions.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 23, 2023
Remove _PyWeakref_GetWeakrefCount() and _PyWeakref_ClearRef() from
the public C API: move them to the internal C API.

Refactor also _weakref_getweakrefs() code to make it more readable.
vstinner added a commit that referenced this issue Jun 23, 2023
Remove _PyWeakref_GetWeakrefCount() and _PyWeakref_ClearRef() from
the public C API: move them to the internal C API.

Refactor also _weakref_getweakrefs() code to make it more readable.
vstinner added a commit to vstinner/cpython that referenced this issue Jun 23, 2023
Deprecate PyWeakref_GetObject() and PyWeakref_GET_OBJECT() functions.
bentasker pushed a commit to bentasker/cpython that referenced this issue Jun 23, 2023
…T_REF() (python#105971)

finalize_modules_clear_weaklist() now holds a strong reference to the
module longer than before: replace PyWeakref_GET_OBJECT() with
_PyWeakref_GET_REF().
vstinner added a commit that referenced this issue Jun 26, 2023
Deprecate PyWeakref_GetObject() and PyWeakref_GET_OBJECT() functions.
erlend-aasland added a commit to erlend-aasland/cpython that referenced this issue Jun 26, 2023
The sqlite3 extension module should be built using the public API, so
we're removing the Py_BUILD_CORE_MODULE define that was introduced as
part of the pythongh-105927 changes.
@vstinner
Copy link
Member Author

Oh, PEP 703 – Making the Global Interpreter Lock Optional in CPython proposes PyWeakref_FetchObject() which is more or less the PyWeakref_GetRef() function that was just added to Python 3.13.

@vstinner
Copy link
Member Author

I added PyWeakref_GetRef() functions and deprecated PyWeakref_GetObject() and PyWeakref_GET_OBJECT() functions. I close the issue.

vstinner added a commit to vstinner/cpython that referenced this issue Jul 9, 2023
PyWeakref_GetRef() now returns 1 on success, and return 0 if the
reference is dead.
vstinner added a commit that referenced this issue Jul 9, 2023
PyWeakref_GetRef() now returns 1 on success, and return 0 if the
reference is dead.

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
topic-C-API type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

3 participants