Skip to content

Commit f9ec0d4

Browse files
committed
Add notes for buffer protocol
1 parent 3718f4b commit f9ec0d4

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

Doc/c-api/typeobj.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3024,6 +3024,22 @@ Buffer Object Structures
30243024

30253025
(5) Return ``0``.
30263026

3027+
**Thread safety:**
3028+
3029+
In :term:`free-threaded <free threading>` Python, implementations must ensure:
3030+
3031+
* The export counter increment in step (3) must be atomic (e.g., using
3032+
:c:func:`_Py_atomic_add_ssize` or equivalent).
3033+
3034+
* The underlying buffer data must remain valid and at a stable memory
3035+
location until all exports are released.
3036+
3037+
* For objects that support resizing or reallocation (like :class:`bytearray`),
3038+
the export counter must be checked atomically before performing such
3039+
operations, and :exc:`BufferError` must be raised if exports exist.
3040+
3041+
* The function must be safe to call concurrently from multiple threads.
3042+
30273043
If *exporter* is part of a chain or tree of buffer providers, two main
30283044
schemes can be used:
30293045

@@ -3069,6 +3085,15 @@ Buffer Object Structures
30693085

30703086
(2) If the counter is ``0``, free all memory associated with *view*.
30713087

3088+
**Thread safety:**
3089+
3090+
In :term:`free-threaded <free threading>` Python:
3091+
3092+
* The export counter decrement in step (1) must be atomic.
3093+
3094+
* Any resource cleanup performed when the counter reaches zero must be
3095+
thread-safe, as multiple threads may release buffers concurrently.
3096+
30723097
The exporter MUST use the :c:member:`~Py_buffer.internal` field to keep
30733098
track of buffer-specific resources. This field is guaranteed to remain
30743099
constant, while a consumer MAY pass a copy of the original buffer as the

Doc/library/stdtypes.rst

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5091,6 +5091,58 @@ copying.
50915091

50925092
.. versionadded:: 3.3
50935093

5094+
.. _thread-safety-memoryview:
5095+
5096+
.. rubric:: Thread safety for memoryview objects
5097+
5098+
:class:`memoryview` objects provide access to the internal data of the
5099+
underlying object without copying. Thread safety depends on both the
5100+
memoryview implementation and the underlying object.
5101+
5102+
The memoryview itself uses atomic operations to track buffer exports
5103+
(each active memoryview counts as one export) in :term:`free-threaded <free threading>`
5104+
Python. Creating, reading attributes of, and releasing a memoryview are
5105+
thread-safe operations.
5106+
5107+
However, the actual data accessed through the memoryview is stored in
5108+
the underlying object. Concurrent access to this data is only safe if
5109+
the underlying object supports it:
5110+
5111+
* For immutable objects like :class:`bytes`, concurrent reads through
5112+
multiple memoryviews are safe.
5113+
5114+
* For mutable objects like :class:`bytearray`, reading and writing the
5115+
same memory region from multiple threads without external
5116+
synchronization is not safe and may result in data corruption.
5117+
Note that even read-only buffer views of mutable objects do not
5118+
prevent data races if the underlying object is modified from
5119+
another thread.
5120+
5121+
.. code-block::
5122+
:class: bad
5123+
5124+
# NOT safe: concurrent writes to the same buffer
5125+
data = bytearray(1000)
5126+
view = memoryview(data)
5127+
# Thread 1: view[0:500] = b'x' * 500
5128+
# Thread 2: view[0:500] = b'y' * 500
5129+
5130+
.. code-block::
5131+
:class: good
5132+
5133+
# Safe: use a lock for concurrent access
5134+
import threading
5135+
lock = threading.Lock()
5136+
data = bytearray(1000)
5137+
view = memoryview(data)
5138+
5139+
with lock:
5140+
view[0:500] = b'x' * 500
5141+
5142+
Modifications to the underlying object (such as resizing a
5143+
:class:`bytearray`) while a memoryview exists may lead to undefined
5144+
behavior, regardless of threading.
5145+
50945146

50955147
.. _types-set:
50965148

Doc/reference/datamodel.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3620,12 +3620,24 @@ implement the protocol in Python.
36203620
provides a convenient way to interpret the flags. The method must return
36213621
a :class:`memoryview` object.
36223622

3623+
**Thread safety:** In :term:`free-threaded <free threading>` Python,
3624+
implementations must ensure thread-safe management of any internal export
3625+
counter using atomic operations. The method must be safe to call
3626+
concurrently from multiple threads, and the returned buffer's underlying
3627+
data must remain valid until the corresponding :meth:`~object.__release_buffer__`
3628+
call completes.
3629+
36233630
.. method:: object.__release_buffer__(self, buffer)
36243631

36253632
Called when a buffer is no longer needed. The *buffer* argument is a
36263633
:class:`memoryview` object that was previously returned by
36273634
:meth:`~object.__buffer__`. The method must release any resources associated
36283635
with the buffer. This method should return ``None``.
3636+
3637+
**Thread safety:** In :term:`free-threaded <free threading>` Python,
3638+
any export counter decrement must use atomic operations. Resource cleanup
3639+
must be thread-safe if multiple threads may release buffers concurrently.
3640+
36293641
Buffer objects that do not need to perform any cleanup are not required
36303642
to implement this method.
36313643

0 commit comments

Comments
 (0)