https://github.com/python/cpython/commit/5b25eaec373430b628a1e591f7312f9bdafe55b2
commit: 5b25eaec373430b628a1e591f7312f9bdafe55b2
branch: main
author: Lysandros Nikolaou <[email protected]>
committer: lysnikolaou <[email protected]>
date: 2026-03-18T17:42:20+01:00
summary:

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

files:
M Doc/c-api/list.rst
M Doc/data/threadsafety.dat

diff --git a/Doc/c-api/list.rst b/Doc/c-api/list.rst
index 758415a76e5cb4..8f560699d355e4 100644
--- a/Doc/c-api/list.rst
+++ b/Doc/c-api/list.rst
@@ -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)
 
diff --git a/Doc/data/threadsafety.dat b/Doc/data/threadsafety.dat
index f063ca1360d5fb..103e8ef3e97ed1 100644
--- a/Doc/data/threadsafety.dat
+++ b/Doc/data/threadsafety.dat
@@ -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:

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to