From 35bd2fda37141e81543c64f1bb9d6773bcce7f73 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 6 Feb 2026 19:24:14 +0100 Subject: [PATCH] gh-142518: Add thread safety notes for the buffer protocol --- Doc/c-api/typeobj.rst | 28 ++++++++++++++++++ Doc/library/stdtypes.rst | 3 ++ Doc/library/threadsafety.rst | 56 ++++++++++++++++++++++++++++++++++++ Doc/reference/datamodel.rst | 13 +++++++++ 4 files changed, 100 insertions(+) diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index 87b488912653b96..c8adcc8d9b507ee 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -3057,6 +3057,24 @@ Buffer Object Structures (5) Return ``0``. + **Thread safety:** + + In the :term:`free-threaded build`, implementations must ensure: + + * The export counter increment in step (3) is atomic. + + * The underlying buffer data remains valid and at a stable memory + location for the lifetime of all exports. + + * For objects that support resizing or reallocation (such as + :class:`bytearray`), the export counter is checked atomically before + such operations, and :exc:`BufferError` is raised if exports exist. + + * The function is safe to call concurrently from multiple threads. + + See also :ref:`thread-safety-memoryview` for the Python-level + thread safety guarantees of :class:`memoryview` objects. + If *exporter* is part of a chain or tree of buffer providers, two main schemes can be used: @@ -3102,6 +3120,16 @@ Buffer Object Structures (2) If the counter is ``0``, free all memory associated with *view*. + **Thread safety:** + + In the :term:`free-threaded build`: + + * The export counter decrement in step (1) must be atomic. + + * Resource cleanup when the counter reaches zero must be reentrant, + as the final release may race with concurrent releases from other + threads. + The exporter MUST use the :c:member:`~Py_buffer.internal` field to keep track of buffer-specific resources. This field is guaranteed to remain constant, while a consumer MAY pass a copy of the original buffer as the diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 6b55daa9b6eae0c..c4a7fa0e56e8b92 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -5040,6 +5040,9 @@ copying. .. versionadded:: 3.3 +For information on the thread safety of :class:`memoryview` objects in +the :term:`free-threaded build`, see :ref:`thread-safety-memoryview`. + .. _types-set: diff --git a/Doc/library/threadsafety.rst b/Doc/library/threadsafety.rst index 7ab5921c7ec2984..7028f39cffcae92 100644 --- a/Doc/library/threadsafety.rst +++ b/Doc/library/threadsafety.rst @@ -342,3 +342,59 @@ thread, iterate over a copy: Consider external synchronization when sharing :class:`dict` instances across threads. + + +.. _thread-safety-memoryview: + +Thread safety for memoryview objects +==================================== + +:class:`memoryview` objects provide access to the internal data of an +underlying object without copying. Thread safety depends on both the +memoryview itself and the underlying buffer exporter. + +The memoryview implementation uses atomic operations to track its own +exports in the :term:`free-threaded build`. Creating and +releasing a memoryview are thread-safe. Attribute access (e.g., +:attr:`~memoryview.shape`, :attr:`~memoryview.format`) reads fields that +are immutable for the lifetime of the memoryview, so concurrent reads +are safe as long as the memoryview has not been released. + +However, the actual data accessed through the memoryview is owned by the +underlying object. Concurrent access to this data is only safe if the +underlying object supports it: + +* For immutable objects like :class:`bytes`, concurrent reads through + multiple memoryviews are safe. + +* For mutable objects like :class:`bytearray`, reading and writing the + same memory region from multiple threads without external + synchronization is not safe and may result in data corruption. + Note that even read-only memoryviews of mutable objects do not + prevent data races if the underlying object is modified from + another thread. + +.. code-block:: + :class: bad + + # NOT safe: concurrent writes to the same buffer + data = bytearray(1000) + view = memoryview(data) + # Thread 1: view[0:500] = b'x' * 500 + # Thread 2: view[0:500] = b'y' * 500 + +.. code-block:: + :class: good + + # Safe: use a lock for concurrent access + import threading + lock = threading.Lock() + data = bytearray(1000) + view = memoryview(data) + + with lock: + view[0:500] = b'x' * 500 + +Resizing or reallocating the underlying object (such as calling +:meth:`bytearray.resize`) while a memoryview is exported raises +:exc:`BufferError`. This is enforced regardless of threading. diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 90b8821daaf3fb3..1e53c0e0e6f9710 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -3637,12 +3637,25 @@ implement the protocol in Python. provides a convenient way to interpret the flags. The method must return a :class:`memoryview` object. + **Thread safety:** In :term:`free-threaded ` Python, + implementations must manage any internal export counter using atomic + operations. The method must be safe to call concurrently from multiple + threads, and the returned buffer's underlying data must remain valid + until the corresponding :meth:`~object.__release_buffer__` call + completes. See :ref:`thread-safety-memoryview` for details. + .. method:: object.__release_buffer__(self, buffer) Called when a buffer is no longer needed. The *buffer* argument is a :class:`memoryview` object that was previously returned by :meth:`~object.__buffer__`. The method must release any resources associated with the buffer. This method should return ``None``. + + **Thread safety:** In :term:`free-threaded ` Python, + any export counter decrement must use atomic operations. Resource + cleanup must be thread-safe, as the final release may race with + concurrent releases from other threads. + Buffer objects that do not need to perform any cleanup are not required to implement this method.