From e4c71c524afb23fdd998593eaa9e807e5d2698e6 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 2 Apr 2026 19:15:18 +0530 Subject: [PATCH 1/5] add thread safety annotations for tuple C-API --- Doc/c-api/tuple.rst | 21 ++++++++--------- Doc/data/threadsafety.dat | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index 3e3752696c46d8..f9fab8655afc3e 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -80,12 +80,10 @@ Tuple Objects :c:func:`Py_NewRef(PyTuple_GetItem(...)) ` or :c:func:`PySequence_GetItem`. - .. c:function:: PyObject* PyTuple_GET_ITEM(PyObject *p, Py_ssize_t pos) Like :c:func:`PyTuple_GetItem`, but does no checking of its arguments. - .. c:function:: PyObject* PyTuple_GetSlice(PyObject *p, Py_ssize_t low, Py_ssize_t high) Return the slice of the tuple pointed to by *p* between *low* and *high*, @@ -99,7 +97,8 @@ Tuple Objects Insert a reference to object *o* at position *pos* of the tuple pointed to by *p*. Return ``0`` on success. If *pos* is out of bounds, return ``-1`` - and set an :exc:`IndexError` exception. + and set an :exc:`IndexError` exception. This function should only be used to fill in brand new tuples; + using it on an existing tuples is thread-unsafe. .. note:: @@ -110,7 +109,7 @@ Tuple Objects .. c:function:: void PyTuple_SET_ITEM(PyObject *p, Py_ssize_t pos, PyObject *o) Like :c:func:`PyTuple_SetItem`, but does no error checking, and should *only* be - used to fill in brand new tuples. + used to fill in brand new tuples. Using it on an existing tuple is thread-unsafe. Bounds checking is performed as an assertion if Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. @@ -122,12 +121,6 @@ Tuple Objects is being replaced; any reference in the tuple at position *pos* will be leaked. - .. warning:: - - This macro should *only* be used on tuples that are newly created. - Using this macro on a tuple that is already in use (or in other words, has - a refcount > 1) could lead to undefined behavior. - .. c:function:: int _PyTuple_Resize(PyObject **p, Py_ssize_t newsize) @@ -142,6 +135,11 @@ Tuple Objects ``*p`` is destroyed. On failure, returns ``-1`` and sets ``*p`` to ``NULL``, and raises :exc:`MemoryError` or :exc:`SystemError`. + .. note:: + + In the :term:`free-threaded build`, this function must only be used on + tuples that are not yet visible to other threads. + .. _struct-sequence-objects: @@ -236,11 +234,12 @@ type. .. c:function:: PyObject* PyStructSequence_GetItem(PyObject *p, Py_ssize_t pos) Return the object at position *pos* in the struct sequence pointed to by *p*. + The returned reference is borrowed from the struct sequence *p* + (that is: it is only valid as long as you hold a reference to *p*). Bounds checking is performed as an assertion if Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. - .. c:function:: PyObject* PyStructSequence_GET_ITEM(PyObject *p, Py_ssize_t pos) Alias to :c:func:`PyStructSequence_GetItem`. diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat index 82edd1167ef128..1f642e2ac2a7dc 100644 --- a/Doc/data/threadsafety.dat +++ b/Doc/data/threadsafety.dat @@ -153,3 +153,52 @@ PyCapsule_SetContext:distinct: # Import - looks up a capsule from a module attribute and # calls PyCapsule_GetPointer; may call arbitrary code PyCapsule_Import:compatible: + +# Tuple objects + +PyTuple_CheckExact:atomic: + +# Creation - pure allocation, no shared state +PyTuple_New:atomic: +PyTuple_FromArray:atomic: +PyTuple_Pack:atomic: + +# Size - tuples are immutable so size never changes +PyTuple_Size:atomic: +PyTuple_GET_SIZE:atomic: + +# Borrowed-reference lookups - tuples are immutable so items +# never change, however the tuple must be kept alive while using the borrowed reference +PyTuple_GetItem:compatible: +PyTuple_GET_ITEM:compatible: + +# Slice - creates a new tuple from an immutable source +PyTuple_GetSlice:atomic: + +# SetItem - only usable on tuples with refcount 1 +PyTuple_SetItem:compatible: + +# SET_ITEM - no synchronization; only for filling in brand new tuples +PyTuple_SET_ITEM:compatible: + +# Resize - only usable on tuples with refcount 1 +_PyTuple_Resize:distinct: + +# Struct Sequence objects + +# Creation +PyStructSequence_NewType:atomic: +PyStructSequence_New:atomic: + +# Initialization - modifies the type object in place +PyStructSequence_InitType:distinct: +PyStructSequence_InitType2:distinct: + +# Borrowed-reference lookups - same as tuple items +PyStructSequence_GetItem:compatible: +PyStructSequence_GET_ITEM:compatible: + +# SetItem - only for filling in brand new instances +PyStructSequence_SetItem:compatible: +PyStructSequence_SET_ITEM:compatible: + From 31da1a33e8c4920c5932930426185d4440e42616 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 2 Apr 2026 19:25:41 +0530 Subject: [PATCH 2/5] reword --- Doc/c-api/tuple.rst | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Doc/c-api/tuple.rst b/Doc/c-api/tuple.rst index f9fab8655afc3e..d3a41352c5cac7 100644 --- a/Doc/c-api/tuple.rst +++ b/Doc/c-api/tuple.rst @@ -80,10 +80,12 @@ Tuple Objects :c:func:`Py_NewRef(PyTuple_GetItem(...)) ` or :c:func:`PySequence_GetItem`. + .. c:function:: PyObject* PyTuple_GET_ITEM(PyObject *p, Py_ssize_t pos) Like :c:func:`PyTuple_GetItem`, but does no checking of its arguments. + .. c:function:: PyObject* PyTuple_GetSlice(PyObject *p, Py_ssize_t low, Py_ssize_t high) Return the slice of the tuple pointed to by *p* between *low* and *high*, @@ -109,7 +111,7 @@ Tuple Objects .. c:function:: void PyTuple_SET_ITEM(PyObject *p, Py_ssize_t pos, PyObject *o) Like :c:func:`PyTuple_SetItem`, but does no error checking, and should *only* be - used to fill in brand new tuples. Using it on an existing tuple is thread-unsafe. + used to fill in brand new tuples, using it on an existing tuple is thread-unsafe. Bounds checking is performed as an assertion if Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. @@ -121,6 +123,12 @@ Tuple Objects is being replaced; any reference in the tuple at position *pos* will be leaked. + .. warning:: + + This macro should *only* be used on tuples that are newly created. + Using this macro on a tuple that is already in use (or in other words, has + a refcount > 1) could lead to undefined behavior. + .. c:function:: int _PyTuple_Resize(PyObject **p, Py_ssize_t newsize) @@ -135,11 +143,6 @@ Tuple Objects ``*p`` is destroyed. On failure, returns ``-1`` and sets ``*p`` to ``NULL``, and raises :exc:`MemoryError` or :exc:`SystemError`. - .. note:: - - In the :term:`free-threaded build`, this function must only be used on - tuples that are not yet visible to other threads. - .. _struct-sequence-objects: @@ -240,6 +243,7 @@ type. Bounds checking is performed as an assertion if Python is built in :ref:`debug mode ` or :option:`with assertions <--with-assertions>`. + .. c:function:: PyObject* PyStructSequence_GET_ITEM(PyObject *p, Py_ssize_t pos) Alias to :c:func:`PyStructSequence_GetItem`. From 03225d05e677575669871e1479da52911da0d383 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 2 Apr 2026 19:28:52 +0530 Subject: [PATCH 3/5] fix _PyTuple_Resize --- Doc/data/threadsafety.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat index 1f642e2ac2a7dc..27539cb2e78115 100644 --- a/Doc/data/threadsafety.dat +++ b/Doc/data/threadsafety.dat @@ -182,7 +182,7 @@ PyTuple_SetItem:compatible: PyTuple_SET_ITEM:compatible: # Resize - only usable on tuples with refcount 1 -_PyTuple_Resize:distinct: +_PyTuple_Resize:compatible: # Struct Sequence objects From 80acb844358d304c136ee5b8f4df05643b17186c Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 2 Apr 2026 19:30:27 +0530 Subject: [PATCH 4/5] minimize changes --- Doc/data/threadsafety.dat | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat index 27539cb2e78115..40a8ea478b21a4 100644 --- a/Doc/data/threadsafety.dat +++ b/Doc/data/threadsafety.dat @@ -172,13 +172,11 @@ PyTuple_GET_SIZE:atomic: PyTuple_GetItem:compatible: PyTuple_GET_ITEM:compatible: -# Slice - creates a new tuple from an immutable source +# Slice - creates a new tuple from an existing tuple PyTuple_GetSlice:atomic: # SetItem - only usable on tuples with refcount 1 PyTuple_SetItem:compatible: - -# SET_ITEM - no synchronization; only for filling in brand new tuples PyTuple_SET_ITEM:compatible: # Resize - only usable on tuples with refcount 1 From cc5192780986d13388f7d99c86f2ad545ef842f8 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 2 Apr 2026 19:31:35 +0530 Subject: [PATCH 5/5] remove type checks for now --- Doc/data/threadsafety.dat | 2 -- 1 file changed, 2 deletions(-) diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat index 40a8ea478b21a4..7c381ecb70c836 100644 --- a/Doc/data/threadsafety.dat +++ b/Doc/data/threadsafety.dat @@ -156,8 +156,6 @@ PyCapsule_Import:compatible: # Tuple objects -PyTuple_CheckExact:atomic: - # Creation - pure allocation, no shared state PyTuple_New:atomic: PyTuple_FromArray:atomic: