gh-128844: Make `_Py_TryIncref` public as an unstable API. (#128926) · python/cpython@d23f570 · GitHub
Skip to content

Commit d23f570

Browse files
colesburyencukou
andauthored
gh-128844: Make _Py_TryIncref public as an unstable API. (#128926)
This exposes `_Py_TryIncref` as `PyUnstable_TryIncref()` and the helper function `_PyObject_SetMaybeWeakref` as `PyUnstable_EnableTryIncRef`. These are helpers for dealing with unowned references in a safe way, particularly in the free threading build. Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent 7dd0a7e commit d23f570

6 files changed

Lines changed: 160 additions & 0 deletions

File tree

Doc/c-api/object.rst

Lines changed: 81 additions & 0 deletions

Include/cpython/object.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,3 +544,9 @@ PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *);
544544

545545
/* Check whether the object is immortal. This cannot fail. */
546546
PyAPI_FUNC(int) PyUnstable_IsImmortal(PyObject *);
547+
548+
// Increments the reference count of the object, if it's not zero.
549+
// PyUnstable_EnableTryIncRef() should be called on the object
550+
// before calling this function in order to avoid spurious failures.
551+
PyAPI_FUNC(int) PyUnstable_TryIncRef(PyObject *);
552+
PyAPI_FUNC(void) PyUnstable_EnableTryIncRef(PyObject *);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :c:func:`PyUnstable_TryIncRef` and :c:func:`PyUnstable_EnableTryIncRef`
2+
unstable APIs. These are helpers for dealing with unowned references in
3+
a thread-safe way, particularly in the free threading build.

Modules/_testcapi/object.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,59 @@ pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj)
131131
return PyLong_FromLong(result);
132132
}
133133

134+
static int MyObject_dealloc_called = 0;
135+
136+
static void
137+
MyObject_dealloc(PyObject *op)
138+
{
139+
// PyUnstable_TryIncRef should return 0 if object is being deallocated
140+
assert(Py_REFCNT(op) == 0);
141+
assert(!PyUnstable_TryIncRef(op));
142+
assert(Py_REFCNT(op) == 0);
143+
144+
MyObject_dealloc_called++;
145+
Py_TYPE(op)->tp_free(op);
146+
}
147+
148+
static PyTypeObject MyType = {
149+
PyVarObject_HEAD_INIT(NULL, 0)
150+
.tp_name = "MyType",
151+
.tp_basicsize = sizeof(PyObject),
152+
.tp_dealloc = MyObject_dealloc,
153+
};
154+
155+
static PyObject *
156+
test_py_try_inc_ref(PyObject *self, PyObject *unused)
157+
{
158+
if (PyType_Ready(&MyType) < 0) {
159+
return NULL;
160+
}
161+
162+
MyObject_dealloc_called = 0;
163+
164+
PyObject *op = PyObject_New(PyObject, &MyType);
165+
if (op == NULL) {
166+
return NULL;
167+
}
168+
169+
PyUnstable_EnableTryIncRef(op);
170+
#ifdef Py_GIL_DISABLED
171+
// PyUnstable_EnableTryIncRef sets the shared flags to
172+
// `_Py_REF_MAYBE_WEAKREF` if the flags are currently zero to ensure that
173+
// the shared reference count is merged on deallocation.
174+
assert((op->ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) >= _Py_REF_MAYBE_WEAKREF);
175+
#endif
176+
177+
if (!PyUnstable_TryIncRef(op)) {
178+
PyErr_SetString(PyExc_AssertionError, "PyUnstable_TryIncRef failed");
179+
Py_DECREF(op);
180+
return NULL;
181+
}
182+
Py_DECREF(op); // undo try-incref
183+
Py_DECREF(op); // dealloc
184+
assert(MyObject_dealloc_called == 1);
185+
Py_RETURN_NONE;
186+
}
134187

135188
static PyMethodDef test_methods[] = {
136189
{"call_pyobject_print", call_pyobject_print, METH_VARARGS},
@@ -139,6 +192,7 @@ static PyMethodDef test_methods[] = {
139192
{"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS},
140193
{"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O},
141194
{"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O},
195+
{"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS},
142196
{NULL},
143197
};
144198

Objects/object.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2588,6 +2588,20 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op)
25882588
#endif
25892589
}
25902590

2591+
int
2592+
PyUnstable_TryIncRef(PyObject *op)
2593+
{
2594+
return _Py_TryIncref(op);
2595+
}
2596+
2597+
void
2598+
PyUnstable_EnableTryIncRef(PyObject *op)
2599+
{
2600+
#ifdef Py_GIL_DISABLED
2601+
_PyObject_SetMaybeWeakref(op);
2602+
#endif
2603+
}
2604+
25912605
void
25922606
_Py_ResurrectReference(PyObject *op)
25932607
{

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 2 additions & 0 deletions

0 commit comments

Comments
 (0)