From 710710374ec46896729d47a419e4fd1835da387e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 3 Sep 2024 19:50:48 +0000 Subject: [PATCH 01/34] Implement PyUnstable_Object_SetDeferredRefcount --- Include/cpython/object.h | 2 ++ Objects/object.c | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index e1024ddbdf60625..021a98361b7930e 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -526,3 +526,5 @@ typedef enum { typedef int (*PyRefTracer)(PyObject *, PyRefTracerEvent event, void *); PyAPI_FUNC(int) PyRefTracer_SetTracer(PyRefTracer tracer, void *data); PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); + +PyAPI_FUNC(int) PyUnstable_Object_SetDeferredRefcount(PyObject *); diff --git a/Objects/object.c b/Objects/object.c index 4b8b6c292668128..7698bc25b74b259 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2482,6 +2482,37 @@ _PyObject_SetDeferredRefcount(PyObject *op) #endif } +int +PyUnstable_Object_SetDeferredRefcount(PyObject *op) +{ +#ifdef Py_GIL_DISABLED + if (!PyType_IS_GC(Py_TYPE(op))) + { + PyErr_SetString(PyExc_TypeError, + "object is not tracked by the garbage collector"); + return -1; + } + + if (!_Py_IsOwnedByCurrentThread(op)) + { + PyErr_SetString(PyExc_ValueError, + "object is not owned by this thread"); + return -1; + } + + if (op->ob_ref_shared != 0) + { + PyErr_SetString(PyExc_ValueError, + "object is already in use by another thread"); + return -1; + } + + _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); + op->ob_ref_shared = _Py_REF_SHARED(_Py_REF_DEFERRED, 0); +#endif + return 0; +} + void _Py_ResurrectReference(PyObject *op) { From 3928ea62cd873d4dc27e51747db68a23f7031307 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 3 Sep 2024 19:50:48 +0000 Subject: [PATCH 02/34] Update documentation. --- Doc/c-api/object.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 1c28f30321bd7a8..bb86e47fee44837 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -575,3 +575,18 @@ Object Protocol has the :c:macro:`Py_TPFLAGS_MANAGED_DICT` flag set. .. versionadded:: 3.13 + +.. c:function:: int PyUnstable_Object_SetDeferredRefcount(PyObject *obj) + + Enable `deferred reference counting https://peps.python.org/pep-0703/#deferred-reference-counting`_ on *obj*. + + *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`), must have been + created by the calling thread, and must not be in use by any existing threads. + + If any of the above conditions are not met, this function returns a negative value + and sets an exception. + + This function is a no-op on builds with the :term:`GIL` enabled. + + .. versionadded:: 3.14 + From 6d2d34ea44d0b3e47a0c97a38c29c1dec3538632 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 3 Sep 2024 19:50:48 +0000 Subject: [PATCH 03/34] Add docstring. --- Include/cpython/object.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 021a98361b7930e..2435313ab935a93 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -527,4 +527,9 @@ typedef int (*PyRefTracer)(PyObject *, PyRefTracerEvent event, void *); PyAPI_FUNC(int) PyRefTracer_SetTracer(PyRefTracer tracer, void *data); PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); +/* Enable PEP-703 deferred reference counting on the object. + * + * If this is called on a build with the GIL enabled, this + * function does nothing. + */ PyAPI_FUNC(int) PyUnstable_Object_SetDeferredRefcount(PyObject *); From 6af0adf35d8d51ec600a242e1c4ef383bfcd9d54 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 3 Sep 2024 19:50:48 +0000 Subject: [PATCH 04/34] Add NEWS entry. --- .../next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst diff --git a/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst b/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst new file mode 100644 index 000000000000000..9346a526539149c --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst @@ -0,0 +1,2 @@ +Added the :c:func:`PyUnstable_Object_SetDeferredRefcount` function for +enabling :pep:`703` deferred reference counting. From 1cc3aaeefbd3e284768eb4fd03452625c92a6de4 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 3 Sep 2024 15:53:47 -0400 Subject: [PATCH 05/34] Fix indentation. --- Doc/c-api/object.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index bb86e47fee44837..a0c35d7ba92d453 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -578,15 +578,15 @@ Object Protocol .. c:function:: int PyUnstable_Object_SetDeferredRefcount(PyObject *obj) - Enable `deferred reference counting https://peps.python.org/pep-0703/#deferred-reference-counting`_ on *obj*. + Enable `deferred reference counting https://peps.python.org/pep-0703/#deferred-reference-counting`_ on *obj*. - *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`), must have been - created by the calling thread, and must not be in use by any existing threads. + *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`), must have been + created by the calling thread, and must not be in use by any existing threads. - If any of the above conditions are not met, this function returns a negative value - and sets an exception. + If any of the above conditions are not met, this function returns a negative value + and sets an exception. - This function is a no-op on builds with the :term:`GIL` enabled. + This function is a no-op on builds with the :term:`GIL` enabled. - .. versionadded:: 3.14 + .. versionadded:: 3.14 From fd087a55e4866d84220b4348496f111add5f4b6a Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Tue, 3 Sep 2024 15:59:49 -0400 Subject: [PATCH 06/34] Fix hyperlink reference. --- Doc/c-api/object.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index a0c35d7ba92d453..fcc206c15eca059 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -578,7 +578,7 @@ Object Protocol .. c:function:: int PyUnstable_Object_SetDeferredRefcount(PyObject *obj) - Enable `deferred reference counting https://peps.python.org/pep-0703/#deferred-reference-counting`_ on *obj*. + Enable `deferred reference counting `_ on *obj*. *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`), must have been created by the calling thread, and must not be in use by any existing threads. From 2dacc0d1162b983b3166563fb456d4f8837a4304 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 12:42:50 -0400 Subject: [PATCH 07/34] Update object.c Co-authored-by: Sam Gross --- Objects/object.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 7698bc25b74b259..09f61e1de958543 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2486,8 +2486,7 @@ int PyUnstable_Object_SetDeferredRefcount(PyObject *op) { #ifdef Py_GIL_DISABLED - if (!PyType_IS_GC(Py_TYPE(op))) - { + if (!PyType_IS_GC(Py_TYPE(op))) { PyErr_SetString(PyExc_TypeError, "object is not tracked by the garbage collector"); return -1; From 0c8b98da8c512acbc31046af01f22568f311cfcc Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 12:43:02 -0400 Subject: [PATCH 08/34] Update object.rst Co-authored-by: Sam Gross --- Doc/c-api/object.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index fcc206c15eca059..0de2e84d876f91d 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -578,7 +578,21 @@ Object Protocol .. c:function:: int PyUnstable_Object_SetDeferredRefcount(PyObject *obj) - Enable `deferred reference counting `_ on *obj*. + Enable `deferred reference counting `_ on *obj*, + if supported by the runtime. In the :term:`free-threaded ` build, + this allows the interpreter to avoid reference count adjustments to *obj*, + which may improve multi-threaded performance. The tradeoff is + that *obj* will only be deallocated by the tracing garbage collector. + + *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`). + The function returns ``-1`` with an error if *obj* is not tracked. + + This function is a no-op on builds with the :term:`GIL` enabled, which do + not support deferred reference counting. + + The function returns ``1`` if deferred reference counting was enabled on *obj*, + and ``0`` if deferred reference counting is not supported or if the hint was + ignored by the runtime. *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`), must have been created by the calling thread, and must not be in use by any existing threads. From 50ff96ea84df06f014c2a2b9464c46d0263b3366 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 15:42:02 -0400 Subject: [PATCH 09/34] Rename to EnableDeferredRefcount --- Doc/c-api/object.rst | 2 +- Include/cpython/object.h | 2 +- .../next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst | 2 +- Objects/object.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 0de2e84d876f91d..4799071d7f09757 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -576,7 +576,7 @@ Object Protocol .. versionadded:: 3.13 -.. c:function:: int PyUnstable_Object_SetDeferredRefcount(PyObject *obj) +.. c:function:: int PyUnstable_Object_EnableDeferredRefcount(PyObject *obj) Enable `deferred reference counting `_ on *obj*, if supported by the runtime. In the :term:`free-threaded ` build, diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 2435313ab935a93..de0c521f2395e96 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -532,4 +532,4 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); * If this is called on a build with the GIL enabled, this * function does nothing. */ -PyAPI_FUNC(int) PyUnstable_Object_SetDeferredRefcount(PyObject *); +PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *); diff --git a/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst b/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst index 9346a526539149c..ac821b5326026e6 100644 --- a/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst +++ b/Misc/NEWS.d/next/C_API/2024-09-03-13-33-33.gh-issue-123619.HhgUUI.rst @@ -1,2 +1,2 @@ -Added the :c:func:`PyUnstable_Object_SetDeferredRefcount` function for +Added the :c:func:`PyUnstable_Object_EnableDeferredRefcount` function for enabling :pep:`703` deferred reference counting. diff --git a/Objects/object.c b/Objects/object.c index 09f61e1de958543..1b0fa91001a331e 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2483,7 +2483,7 @@ _PyObject_SetDeferredRefcount(PyObject *op) } int -PyUnstable_Object_SetDeferredRefcount(PyObject *op) +PyUnstable_Object_EnableDeferredRefcount(PyObject *op) { #ifdef Py_GIL_DISABLED if (!PyType_IS_GC(Py_TYPE(op))) { From 6be928690573a237defff2472630a4d43ada7ac5 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 15:45:23 -0400 Subject: [PATCH 10/34] No-op if the object already has DRC enabled --- Objects/object.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 1b0fa91001a331e..170b9259c9b08af 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2499,12 +2499,9 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return -1; } - if (op->ob_ref_shared != 0) - { - PyErr_SetString(PyExc_ValueError, - "object is already in use by another thread"); - return -1; - } + if (_PyObject_HasDeferredRefcount(op)) + // Nothing to do + return 0; _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); op->ob_ref_shared = _Py_REF_SHARED(_Py_REF_DEFERRED, 0); From 10f3a2d93998e77e4d59ec0318ccda6a3338cc1d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 15:46:05 -0400 Subject: [PATCH 11/34] Fix indentation. --- Objects/object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index 170b9259c9b08af..0308dcb8bd4a6a7 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2488,7 +2488,7 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) #ifdef Py_GIL_DISABLED if (!PyType_IS_GC(Py_TYPE(op))) { PyErr_SetString(PyExc_TypeError, - "object is not tracked by the garbage collector"); + "object is not tracked by the garbage collector"); return -1; } From 78889d53407f40fd5ede16d04a2f00259e28a7e0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 15:48:50 -0400 Subject: [PATCH 12/34] Use 1 for success. --- Objects/object.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index 0308dcb8bd4a6a7..c3a7133522e855c 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2505,8 +2505,10 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); op->ob_ref_shared = _Py_REF_SHARED(_Py_REF_DEFERRED, 0); -#endif + return 1; +#else return 0; +#endif } void From fe2cb4624888ed698604942b7cf59d62a8d279d4 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 15:49:15 -0400 Subject: [PATCH 13/34] Remove redundant line. --- Doc/c-api/object.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 4799071d7f09757..1c9849877fae890 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -600,7 +600,5 @@ Object Protocol If any of the above conditions are not met, this function returns a negative value and sets an exception. - This function is a no-op on builds with the :term:`GIL` enabled. - .. versionadded:: 3.14 From 7b95459ef31621165c3442e5b622194f282a1ae9 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Thu, 19 Sep 2024 16:37:07 -0400 Subject: [PATCH 14/34] Add simple unit test. --- Lib/test/test_capi/test_object.py | 19 +++++++++++++++++++ Modules/_testcapi/object.c | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index cc9c9b688f00e21..a4e6e393a54c589 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -1,7 +1,9 @@ import enum import unittest +from test import support from test.support import import_helper from test.support import os_helper +from threading import Thread _testlimitedcapi = import_helper.import_module('_testlimitedcapi') _testcapi = import_helper.import_module('_testcapi') @@ -130,6 +132,23 @@ def test_ClearWeakRefsNoCallbacks_no_weakref_support(self): ref = weakref.ref(obj) _testcapi.pyobject_clear_weakrefs_no_callbacks(obj) +class EnableDeferredRefcountingTest(unittest.TestCase): + """Test PyUnstable_Object_EnableDeferredRefcount""" + def test_enable_deferred_refcount(self): + if support.Py_GIL_DISABLED: + with self.assertRaises(TypeError): + _testcapi.pyobject_enable_deferred_refcount("not tracked") + + if support.Py_GIL_DISABLED: + def foo(obj): + with self.assertRaises(ValueError): + _testcapi.pyobject_enable_deferred_refcount(obj) + + thread = Thread(target=foo, args=([],)) + thread.start() + thread.join() + + self.assertEqual(_testcapi.pyobject_enable_deferred_refcount([]), int(support.Py_GIL_DISABLED)) if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index 1c76e766a790f07..e1224c7a5ec3e41 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -124,13 +124,24 @@ pyobject_clear_weakrefs_no_callbacks(PyObject *self, PyObject *obj) Py_RETURN_NONE; } +static PyObject * +pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj) +{ + int result = PyUnstable_Object_EnableDeferredRefcount(obj); + if (result < 0) { + return NULL; + } + + return PyLong_FromLong(result); +} + static PyMethodDef test_methods[] = { {"call_pyobject_print", call_pyobject_print, METH_VARARGS}, {"pyobject_print_null", pyobject_print_null, METH_VARARGS}, {"pyobject_print_noref_object", pyobject_print_noref_object, METH_VARARGS}, {"pyobject_print_os_error", pyobject_print_os_error, METH_VARARGS}, {"pyobject_clear_weakrefs_no_callbacks", pyobject_clear_weakrefs_no_callbacks, METH_O}, - + {"pyobject_enable_deferred_refcount", pyobject_enable_deferred_refcount, METH_O}, {NULL}, }; From 042f610d8b8445df6d80eea5f037ac7eba6e4d5f Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 7 Oct 2024 13:17:31 +0000 Subject: [PATCH 15/34] Make thread-safe. --- Objects/object.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index c3a7133522e855c..096489cea8bfa0f 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2492,19 +2492,12 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return -1; } - if (!_Py_IsOwnedByCurrentThread(op)) - { - PyErr_SetString(PyExc_ValueError, - "object is not owned by this thread"); - return -1; - } - if (_PyObject_HasDeferredRefcount(op)) // Nothing to do return 0; _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); - op->ob_ref_shared = _Py_REF_SHARED(_Py_REF_DEFERRED, 0); + _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); return 1; #else return 0; From c1d9c74195b53f62d78bf37f65711041d4d5ca49 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 7 Oct 2024 13:25:00 +0000 Subject: [PATCH 16/34] Fix test. --- Lib/test/test_capi/test_object.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index a4e6e393a54c589..bb4965d1d230648 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -141,12 +141,17 @@ def test_enable_deferred_refcount(self): if support.Py_GIL_DISABLED: def foo(obj): - with self.assertRaises(ValueError): - _testcapi.pyobject_enable_deferred_refcount(obj) + _testcapi.pyobject_enable_deferred_refcount(obj) - thread = Thread(target=foo, args=([],)) - thread.start() - thread.join() + x = [] + y = [x] # Increase x's refcnt + threads = [Thread(target=foo, args=(x,)) for _ in range(5)] + + for thread in threads: + thread.start() + + for thread in threads: + thread.join() self.assertEqual(_testcapi.pyobject_enable_deferred_refcount([]), int(support.Py_GIL_DISABLED)) From a88aa31821bfbec09d92f6279eea615ecaff4cf0 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 7 Oct 2024 13:29:54 +0000 Subject: [PATCH 17/34] Add owned-by-current-thread check back. --- Lib/test/test_capi/test_object.py | 7 +++++-- Objects/object.c | 8 +++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index bb4965d1d230648..9775dde590748c2 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -141,10 +141,13 @@ def test_enable_deferred_refcount(self): if support.Py_GIL_DISABLED: def foo(obj): - _testcapi.pyobject_enable_deferred_refcount(obj) + obj.append(1) # Do something with it from another thread + + with self.assertRaises(TypeError): + _testcapi.pyobject_enable_deferred_refcount(obj) x = [] - y = [x] # Increase x's refcnt + self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(x), int(support.Py_GIL_DISABLED)) threads = [Thread(target=foo, args=(x,)) for _ in range(5)] for thread in threads: diff --git a/Objects/object.c b/Objects/object.c index 096489cea8bfa0f..3464f7aa6099611 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2492,12 +2492,18 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return -1; } + if (!_Py_IsOwnedByCurrentThread(op)) { + PyErr_SetString(PyExc_TypeError, "object is not owned by this thread"); + return -1; + } + if (_PyObject_HasDeferredRefcount(op)) // Nothing to do return 0; + // This is already atomic _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); - _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); + op->ob_ref_shared = _Py_REF_SHARED(_Py_REF_DEFERRED, 0); return 1; #else return 0; From 3f31693ec4ab53d159c1749cc57dd07794509472 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 7 Oct 2024 16:49:27 -0400 Subject: [PATCH 18/34] Use atomic storage for ob_ref_shared --- Lib/test/test_capi/test_object.py | 3 +-- Objects/object.c | 8 +------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 9775dde590748c2..0f468e0996b3886 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -143,8 +143,7 @@ def test_enable_deferred_refcount(self): def foo(obj): obj.append(1) # Do something with it from another thread - with self.assertRaises(TypeError): - _testcapi.pyobject_enable_deferred_refcount(obj) + self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(obj), 0) x = [] self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(x), int(support.Py_GIL_DISABLED)) diff --git a/Objects/object.c b/Objects/object.c index 3464f7aa6099611..096489cea8bfa0f 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2492,18 +2492,12 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return -1; } - if (!_Py_IsOwnedByCurrentThread(op)) { - PyErr_SetString(PyExc_TypeError, "object is not owned by this thread"); - return -1; - } - if (_PyObject_HasDeferredRefcount(op)) // Nothing to do return 0; - // This is already atomic _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); - op->ob_ref_shared = _Py_REF_SHARED(_Py_REF_DEFERRED, 0); + _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); return 1; #else return 0; From cedf106a4362999fda31bfebb1eecb355c86fc33 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 21 Oct 2024 10:06:08 -0400 Subject: [PATCH 19/34] No-op if the object is not a GC type. --- Lib/test/test_capi/test_object.py | 40 ++++++++++++++++++------------- Modules/_testcapi/object.c | 4 ---- Objects/object.c | 9 +++---- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 0f468e0996b3886..dafb16a44ca0822 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -3,7 +3,7 @@ from test import support from test.support import import_helper from test.support import os_helper -from threading import Thread +from test.support import threading_helper _testlimitedcapi = import_helper.import_module('_testlimitedcapi') _testcapi = import_helper.import_module('_testcapi') @@ -132,30 +132,38 @@ def test_ClearWeakRefsNoCallbacks_no_weakref_support(self): ref = weakref.ref(obj) _testcapi.pyobject_clear_weakrefs_no_callbacks(obj) + class EnableDeferredRefcountingTest(unittest.TestCase): """Test PyUnstable_Object_EnableDeferredRefcount""" def test_enable_deferred_refcount(self): - if support.Py_GIL_DISABLED: - with self.assertRaises(TypeError): - _testcapi.pyobject_enable_deferred_refcount("not tracked") + from threading import Thread - if support.Py_GIL_DISABLED: - def foo(obj): - obj.append(1) # Do something with it from another thread + self.assertEqual(_testcapi.pyobject_enable_deferred_refcount("not tracked"), 0) + self.assertEqual(_testcapi.pyobject_enable_deferred_refcount([]), int(support.Py_GIL_DISABLED)) - self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(obj), 0) + # Make sure that PyUnstable_Object_EnableDeferredRefcount is thread safe + def silly_func(obj): + self.assertEqual( + _testcapi.pyobject_enable_deferred_refcount(obj), + int(support.Py_GIL_DISABLED) + ) - x = [] - self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(x), int(support.Py_GIL_DISABLED)) - threads = [Thread(target=foo, args=(x,)) for _ in range(5)] + silly_list = [1, 2, 3] + threads = [Thread(target=silly_func, args=(silly_list,)) for _ in range(5)] - for thread in threads: - thread.start() + with threading_helper.catch_threading_exception() as cm: + for t in threads: + t.start() + + for i in range(10): + silly_list.append(i) + + for t in threads: + t.join() + + self.assertIsNone(cm.exc_value) - for thread in threads: - thread.join() - self.assertEqual(_testcapi.pyobject_enable_deferred_refcount([]), int(support.Py_GIL_DISABLED)) if __name__ == "__main__": unittest.main() diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c index e1224c7a5ec3e41..3af5429ef00985e 100644 --- a/Modules/_testcapi/object.c +++ b/Modules/_testcapi/object.c @@ -128,10 +128,6 @@ static PyObject * pyobject_enable_deferred_refcount(PyObject *self, PyObject *obj) { int result = PyUnstable_Object_EnableDeferredRefcount(obj); - if (result < 0) { - return NULL; - } - return PyLong_FromLong(result); } diff --git a/Objects/object.c b/Objects/object.c index 096489cea8bfa0f..f87b6e0f790d86d 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2487,14 +2487,15 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) { #ifdef Py_GIL_DISABLED if (!PyType_IS_GC(Py_TYPE(op))) { - PyErr_SetString(PyExc_TypeError, - "object is not tracked by the garbage collector"); - return -1; + // Deferred reference counting doesn't work + // on untracked types. + return 0; } - if (_PyObject_HasDeferredRefcount(op)) + if (_PyObject_HasDeferredRefcount(op)) { // Nothing to do return 0; + } _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); From b5f8df6c97dfb52c854788ba374db7b695714d38 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 21 Oct 2024 10:10:46 -0400 Subject: [PATCH 20/34] Update the documentation. --- Doc/c-api/object.rst | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 1c9849877fae890..2d3d1b7d1b4b8eb 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -584,21 +584,14 @@ Object Protocol which may improve multi-threaded performance. The tradeoff is that *obj* will only be deallocated by the tracing garbage collector. - *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`). - The function returns ``-1`` with an error if *obj* is not tracked. - - This function is a no-op on builds with the :term:`GIL` enabled, which do - not support deferred reference counting. - The function returns ``1`` if deferred reference counting was enabled on *obj*, and ``0`` if deferred reference counting is not supported or if the hint was - ignored by the runtime. + ignored by the runtime. This function cannot fail. - *obj* must be an object tracked by the garbage collector (see :func:`gc.is_tracked`), must have been - created by the calling thread, and must not be in use by any existing threads. - - If any of the above conditions are not met, this function returns a negative value - and sets an exception. + This function is a no-op on builds with the :term:`GIL` enabled, which do + not support deferred reference counting. This also no-ops if *obj* is not + an object tracked by the garbage collector (see :func:`gc.is_tracked` and + :c:func:`PyObject_GC_IsTracked`). - .. versionadded:: 3.14 + .. versionadded:: next From 7e2653ab9d48c40d89f9c43cce0cacbdb696a4b6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 21 Oct 2024 10:11:35 -0400 Subject: [PATCH 21/34] Update the docstring. --- Include/cpython/object.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index de0c521f2395e96..0b8de4481f00881 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -529,7 +529,7 @@ PyAPI_FUNC(PyRefTracer) PyRefTracer_GetTracer(void**); /* Enable PEP-703 deferred reference counting on the object. * - * If this is called on a build with the GIL enabled, this - * function does nothing. + * Returns 1 if deferred reference counting was successfully enabled, and + * 0 if the runtime ignored it. This function cannot fail. */ PyAPI_FUNC(int) PyUnstable_Object_EnableDeferredRefcount(PyObject *); From b831c777d4a9a3c7eb86a36f639443abab71aa36 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 21 Oct 2024 10:13:18 -0400 Subject: [PATCH 22/34] Clarify thread safety in the documentation. --- Doc/c-api/object.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 2d3d1b7d1b4b8eb..d126c65f4a5bf21 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -584,9 +584,9 @@ Object Protocol which may improve multi-threaded performance. The tradeoff is that *obj* will only be deallocated by the tracing garbage collector. - The function returns ``1`` if deferred reference counting was enabled on *obj*, + This function returns ``1`` if deferred reference counting was enabled on *obj*, and ``0`` if deferred reference counting is not supported or if the hint was - ignored by the runtime. This function cannot fail. + ignored by the runtime. This function is thread-safe, and cannot fail. This function is a no-op on builds with the :term:`GIL` enabled, which do not support deferred reference counting. This also no-ops if *obj* is not From d0b5ed2e40990c1d95da675ea5ba92e07501fbe2 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 21 Oct 2024 11:24:43 -0400 Subject: [PATCH 23/34] Fix thread safety on GC bits. --- Lib/test/test_capi/test_object.py | 16 +++++++++++++--- Modules/_testinternalcapi.c | 7 +++++++ Objects/object.c | 8 ++++++-- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index dafb16a44ca0822..bb8f6d7bea6b94a 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -7,6 +7,7 @@ _testlimitedcapi = import_helper.import_module('_testlimitedcapi') _testcapi = import_helper.import_module('_testcapi') +_testinternalcapi = import_helper.import_module('_testinternalcapi') class Constant(enum.IntEnum): @@ -139,13 +140,19 @@ def test_enable_deferred_refcount(self): from threading import Thread self.assertEqual(_testcapi.pyobject_enable_deferred_refcount("not tracked"), 0) - self.assertEqual(_testcapi.pyobject_enable_deferred_refcount([]), int(support.Py_GIL_DISABLED)) + foo = [] + self.assertEqual(_testcapi.pyobject_enable_deferred_refcount(foo), int(support.Py_GIL_DISABLED)) + + # Make sure reference counting works on foo now + self.assertEqual(foo, []) + if support.Py_GIL_DISABLED: + self.assertTrue(_testinternalcapi.has_deferred_refcount(foo)) # Make sure that PyUnstable_Object_EnableDeferredRefcount is thread safe def silly_func(obj): - self.assertEqual( + self.assertIn( _testcapi.pyobject_enable_deferred_refcount(obj), - int(support.Py_GIL_DISABLED) + (0, 1) ) silly_list = [1, 2, 3] @@ -163,6 +170,9 @@ def silly_func(obj): self.assertIsNone(cm.exc_value) + if support.Py_GIL_DISABLED: + self.assertTrue(_testinternalcapi.has_deferred_refcount(silly_list)) + if __name__ == "__main__": diff --git a/Modules/_testinternalcapi.c b/Modules/_testinternalcapi.c index 0451688a46c75f2..1c9179a1358f2d2 100644 --- a/Modules/_testinternalcapi.c +++ b/Modules/_testinternalcapi.c @@ -2049,6 +2049,12 @@ identify_type_slot_wrappers(PyObject *self, PyObject *Py_UNUSED(ignored)) } +static PyObject * +has_deferred_refcount(PyObject *self, PyObject *op) +{ + return PyBool_FromLong(_PyObject_HasDeferredRefcount(op)); +} + static PyMethodDef module_functions[] = { {"get_configs", get_configs, METH_NOARGS}, {"get_recursion_depth", get_recursion_depth, METH_NOARGS}, @@ -2145,6 +2151,7 @@ static PyMethodDef module_functions[] = { GH_119213_GETARGS_METHODDEF {"get_static_builtin_types", get_static_builtin_types, METH_NOARGS}, {"identify_type_slot_wrappers", identify_type_slot_wrappers, METH_NOARGS}, + {"has_deferred_refcount", has_deferred_refcount, METH_O}, {NULL, NULL} /* sentinel */ }; diff --git a/Objects/object.c b/Objects/object.c index f87b6e0f790d86d..e3cb2517f3e2a44 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2497,8 +2497,12 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return 0; } - _PyObject_SET_GC_BITS(op, _PyGC_BITS_DEFERRED); - _Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); + uint8_t bits = _Py_atomic_load_uint8(&op->ob_gc_bits); + if (_Py_atomic_compare_exchange_uint8(&op->ob_gc_bits, &bits, bits | _Py_REF_DEFERRED) == 0) + { + return 0; + } + _Py_atomic_store_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); return 1; #else return 0; From f2b2c78c0506e07995c6d149dc8558924269f833 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 21 Oct 2024 13:37:54 -0400 Subject: [PATCH 24/34] Retain old reference count. --- Objects/object.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index e3cb2517f3e2a44..7d3110ec64c0061 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2498,11 +2498,13 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) } uint8_t bits = _Py_atomic_load_uint8(&op->ob_gc_bits); - if (_Py_atomic_compare_exchange_uint8(&op->ob_gc_bits, &bits, bits | _Py_REF_DEFERRED) == 0) + if (_Py_atomic_compare_exchange_uint8(&op->ob_gc_bits, &bits, bits | _PyGC_BITS_DEFERRED) == 0) { + // Someone beat us to it! return 0; } - _Py_atomic_store_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); + Py_ssize_t shared = _Py_atomic_load_ssize(&op->ob_ref_shared); + _Py_atomic_store_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, shared)); return 1; #else return 0; From 5728bc307a0aee0d0c2b73986a789480162214f7 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 2 Nov 2024 12:58:32 -0400 Subject: [PATCH 25/34] Require CPU resources for tests. --- Lib/test/test_capi/test_object.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index bb8f6d7bea6b94a..4975cb0b6ef83de 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -136,6 +136,7 @@ def test_ClearWeakRefsNoCallbacks_no_weakref_support(self): class EnableDeferredRefcountingTest(unittest.TestCase): """Test PyUnstable_Object_EnableDeferredRefcount""" + @support.requires_resources("cpu") def test_enable_deferred_refcount(self): from threading import Thread @@ -156,7 +157,9 @@ def silly_func(obj): ) silly_list = [1, 2, 3] - threads = [Thread(target=silly_func, args=(silly_list,)) for _ in range(5)] + threads = [ + Thread(target=silly_func, args=(silly_list,)) for _ in range(5) + ] with threading_helper.catch_threading_exception() as cm: for t in threads: From e6dd1f73456ba649d3e54cc13998d360a9006d5d Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 2 Nov 2024 13:00:37 -0400 Subject: [PATCH 26/34] Fix typo. --- Lib/test/test_capi/test_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index 4975cb0b6ef83de..bc409b82ebb69b7 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -136,7 +136,7 @@ def test_ClearWeakRefsNoCallbacks_no_weakref_support(self): class EnableDeferredRefcountingTest(unittest.TestCase): """Test PyUnstable_Object_EnableDeferredRefcount""" - @support.requires_resources("cpu") + @support.requires_resource("cpu") def test_enable_deferred_refcount(self): from threading import Thread From 85ce7dd490ac8870cc50dbcf0d06790be7a63b5e Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 2 Nov 2024 13:04:05 -0400 Subject: [PATCH 27/34] Add a what's new entry. --- Doc/whatsnew/3.14.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 83c13e6fb64d1dd..b0520000c5829f6 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -528,6 +528,8 @@ New Features (Contributed by Victor Stinner in :gh:`107954`.) +* Add :c:func:`PyUnstable_Object_EnableDeferredRefcount` for enabling + deferred reference counting, as outlined in :pep:`703`. Porting to Python 3.14 ---------------------- From 1e4413fa62b990b0ed7f6fc70a4bcc2eb032b542 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 2 Nov 2024 13:06:46 -0400 Subject: [PATCH 28/34] Remove extra newline. --- Lib/test/test_capi/test_object.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_capi/test_object.py b/Lib/test/test_capi/test_object.py index bc409b82ebb69b7..a38b203ed12fa27 100644 --- a/Lib/test/test_capi/test_object.py +++ b/Lib/test/test_capi/test_object.py @@ -177,6 +177,5 @@ def silly_func(obj): self.assertTrue(_testinternalcapi.has_deferred_refcount(silly_list)) - if __name__ == "__main__": unittest.main() From 99e9b64a15f4b666a20ac4b0e2ad3deb5d156685 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 2 Nov 2024 13:20:31 -0400 Subject: [PATCH 29/34] Fix retainment of shared reference count. --- Objects/object.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/object.c b/Objects/object.c index 4378ed49c942fc7..7828a022537d7e0 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2541,7 +2541,7 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return 0; } Py_ssize_t shared = _Py_atomic_load_ssize(&op->ob_ref_shared); - _Py_atomic_store_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, shared)); + _Py_atomic_store_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0) + shared); return 1; #else return 0; From d8e8af8d5ad1a50298798bc34b205cb775ed7b48 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Nov 2024 10:03:42 -0500 Subject: [PATCH 30/34] Update object.rst Co-authored-by: Petr Viktorin --- Doc/c-api/object.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index b4af69c096d9b29..2eca251cb7d5cc1 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -584,7 +584,8 @@ Object Protocol which may improve multi-threaded performance. The tradeoff is that *obj* will only be deallocated by the tracing garbage collector. - This function returns ``1`` if deferred reference counting was enabled on *obj*, + This function returns ``1`` if deferred reference counting is enabled on *obj* + (including when it was enabled before the call), and ``0`` if deferred reference counting is not supported or if the hint was ignored by the runtime. This function is thread-safe, and cannot fail. From 7b3510db72dcc1c545684d70a769e6db687d2466 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Nov 2024 10:05:19 -0500 Subject: [PATCH 31/34] Update object.rst Co-authored-by: Petr Viktorin --- Doc/c-api/object.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index 2eca251cb7d5cc1..c81ff660cdf6f04 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -589,8 +589,8 @@ Object Protocol and ``0`` if deferred reference counting is not supported or if the hint was ignored by the runtime. This function is thread-safe, and cannot fail. - This function is a no-op on builds with the :term:`GIL` enabled, which do - not support deferred reference counting. This also no-ops if *obj* is not + This function does nothing on builds with the :term:`GIL` enabled, which do + not support deferred reference counting. This also does nothing if *obj* is not an object tracked by the garbage collector (see :func:`gc.is_tracked` and :c:func:`PyObject_GC_IsTracked`). From 4c29547e443d9ee4a1fc1f150220e83db0270658 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Nov 2024 10:33:23 -0500 Subject: [PATCH 32/34] Update Doc/c-api/object.rst Co-authored-by: Petr Viktorin --- Doc/c-api/object.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Doc/c-api/object.rst b/Doc/c-api/object.rst index c81ff660cdf6f04..1e1cf6e6bfd7e9b 100644 --- a/Doc/c-api/object.rst +++ b/Doc/c-api/object.rst @@ -594,5 +594,8 @@ Object Protocol an object tracked by the garbage collector (see :func:`gc.is_tracked` and :c:func:`PyObject_GC_IsTracked`). + This function is intended to be used soon after *obj* is created, + by the code that creates it. + .. versionadded:: next From 55443b64b518f8343c952791a309cfdc8c2c5286 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Nov 2024 10:43:34 -0500 Subject: [PATCH 33/34] Use _Py_atomic_add_ssize instead of store. --- Objects/object.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 7828a022537d7e0..794ba13d33be912 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2540,8 +2540,7 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) // Someone beat us to it! return 0; } - Py_ssize_t shared = _Py_atomic_load_ssize(&op->ob_ref_shared); - _Py_atomic_store_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0) + shared); + _Py_atomic_add_ssize(&op->ob_ref_shared, _Py_REF_SHARED(_Py_REF_DEFERRED, 0)); return 1; #else return 0; From 1a20d14a44a97bb5955c0f2fa388d36a5ac0a732 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Mon, 4 Nov 2024 11:25:34 -0500 Subject: [PATCH 34/34] Don't load the GC bits more than once. --- Objects/object.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Objects/object.c b/Objects/object.c index 794ba13d33be912..052dea9ad1feff0 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2529,12 +2529,13 @@ PyUnstable_Object_EnableDeferredRefcount(PyObject *op) return 0; } - if (_PyObject_HasDeferredRefcount(op)) { - // Nothing to do + uint8_t bits = _Py_atomic_load_uint8(&op->ob_gc_bits); + if ((bits & _PyGC_BITS_DEFERRED) != 0) + { + // Nothing to do. return 0; } - uint8_t bits = _Py_atomic_load_uint8(&op->ob_gc_bits); if (_Py_atomic_compare_exchange_uint8(&op->ob_gc_bits, &bits, bits | _PyGC_BITS_DEFERRED) == 0) { // Someone beat us to it!