gh-142518: Annotate PyList_* C APIs for thread safety (#146109)

This commit is contained in:
Lysandros Nikolaou 2026-03-18 17:42:20 +01:00 committed by GitHub
parent 1e4ed93210
commit 5b25eaec37
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 100 additions and 0 deletions

View file

@ -74,11 +74,25 @@ List Objects
Like :c:func:`PyList_GetItemRef`, but returns a
:term:`borrowed reference` instead of a :term:`strong reference`.
.. note::
In the :term:`free-threaded build`, the returned
:term:`borrowed reference` may become invalid if another thread modifies
the list concurrently. Prefer :c:func:`PyList_GetItemRef`, which returns
a :term:`strong reference`.
.. c:function:: PyObject* PyList_GET_ITEM(PyObject *list, Py_ssize_t i)
Similar to :c:func:`PyList_GetItem`, but without error checking.
.. note::
In the :term:`free-threaded build`, the returned
:term:`borrowed reference` may become invalid if another thread modifies
the list concurrently. Prefer :c:func:`PyList_GetItemRef`, which returns
a :term:`strong reference`.
.. c:function:: int PyList_SetItem(PyObject *list, Py_ssize_t index, PyObject *item)
@ -108,6 +122,14 @@ List Objects
is being replaced; any reference in *list* at position *i* will be
leaked.
.. note::
In the :term:`free-threaded build`, this macro has no internal
synchronization. It is normally only used to fill in new lists where no
other thread has a reference to the list. If the list may be shared,
use :c:func:`PyList_SetItem` instead, which uses a :term:`per-object
lock`.
.. c:function:: int PyList_Insert(PyObject *list, Py_ssize_t index, PyObject *item)
@ -138,6 +160,12 @@ List Objects
Return ``0`` on success, ``-1`` on failure. Indexing from the end of the
list is not supported.
.. note::
In the :term:`free-threaded build`, when *itemlist* is a :class:`list`,
both *list* and *itemlist* are locked for the duration of the operation.
For other iterables (or ``NULL``), only *list* is locked.
.. c:function:: int PyList_Extend(PyObject *list, PyObject *iterable)
@ -150,6 +178,14 @@ List Objects
.. versionadded:: 3.13
.. note::
In the :term:`free-threaded build`, when *iterable* is a :class:`list`,
:class:`set`, :class:`dict`, or dict view, both *list* and *iterable*
(or its underlying dict) are locked for the duration of the operation.
For other iterables, only *list* is locked; *iterable* may be
concurrently modified by another thread.
.. c:function:: int PyList_Clear(PyObject *list)
@ -168,6 +204,14 @@ List Objects
Sort the items of *list* in place. Return ``0`` on success, ``-1`` on
failure. This is equivalent to ``list.sort()``.
.. note::
In the :term:`free-threaded build`, element comparison via
:meth:`~object.__lt__` can execute arbitrary Python code, during which
the :term:`per-object lock` may be temporarily released. For built-in
types (:class:`str`, :class:`int`, :class:`float`), the lock is not
released during comparison.
.. c:function:: int PyList_Reverse(PyObject *list)

View file

@ -17,3 +17,59 @@
PyMutex_Lock:shared:
PyMutex_Unlock:shared:
PyMutex_IsLocked:atomic:
# List objects (Doc/c-api/list.rst)
# Type checks - read ob_type pointer, always safe
PyList_Check:atomic:
PyList_CheckExact:atomic:
# Creation - pure allocation, no shared state
PyList_New:atomic:
# Size - uses atomic load on free-threaded builds
PyList_Size:atomic:
PyList_GET_SIZE:atomic:
# Strong-reference lookup - lock-free with atomic ops
PyList_GetItemRef:atomic:
# Borrowed-reference lookups - no locking; returned borrowed
# reference is unsafe in free-threaded builds without
# external synchronization
PyList_GetItem:compatible:
PyList_GET_ITEM:compatible:
# Single-item mutations - hold per-object lock for duration;
# appear atomic to lock-free readers
PyList_SetItem:atomic:
PyList_Append:atomic:
# Insert - protected by per-object critical section; shifts
# elements so lock-free readers may observe intermediate states
PyList_Insert:shared:
# Initialization macro - no synchronization; normally only used
# to fill in new lists where there is no previous content
PyList_SET_ITEM:compatible:
# Bulk operations - hold per-object lock for duration
PyList_GetSlice:atomic:
PyList_AsTuple:atomic:
PyList_Clear:atomic:
# Reverse - protected by per-object critical section; swaps
# elements so lock-free readers may observe intermediate states
PyList_Reverse:shared:
# Slice assignment - lock target list; also lock source when it
# is a list
PyList_SetSlice:shared:
# Sort - per-object lock held; comparison callbacks may execute
# arbitrary Python code
PyList_Sort:shared:
# Extend - lock target list; also lock source when it is a
# list, set, or dict
PyList_Extend:shared: