Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Doc/c-api/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand All @@ -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)

Expand Down
28 changes: 28 additions & 0 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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 done atomically,
as the final release may race with concurrent releases from other
threads and dellocation must only happen once.

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
Expand Down
56 changes: 56 additions & 0 deletions Doc/data/threadsafety.dat
Original file line number Diff line number Diff line change
Expand Up @@ -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:
3 changes: 3 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5045,6 +5045,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:

Expand Down
56 changes: 56 additions & 0 deletions Doc/library/threadsafety.rst
Original file line number Diff line number Diff line change
Expand Up @@ -548,3 +548,59 @@ Thread safety for bytearray objects

Consider external synchronization when sharing :class:`bytearray` instances
across threads. See :ref:`freethreading-python-howto` for more information.


.. _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.
69 changes: 62 additions & 7 deletions Doc/library/wave.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,19 @@
--------------

The :mod:`!wave` module provides a convenient interface to the Waveform Audio
"WAVE" (or "WAV") file format. Only uncompressed PCM encoded wave files are
supported.
"WAVE" (or "WAV") file format.

The module supports uncompressed PCM and IEEE floating-point WAV formats.

.. versionchanged:: 3.12

Support for ``WAVE_FORMAT_EXTENSIBLE`` headers was added, provided that the
extended format is ``KSDATAFORMAT_SUBTYPE_PCM``.

.. versionchanged:: next

Support for reading and writing ``WAVE_FORMAT_IEEE_FLOAT`` files was added.

The :mod:`!wave` module defines the following function and exception:


Expand Down Expand Up @@ -60,6 +65,21 @@ The :mod:`!wave` module defines the following function and exception:
specification or hits an implementation deficiency.


.. data:: WAVE_FORMAT_PCM

Format code for uncompressed PCM audio.


.. data:: WAVE_FORMAT_IEEE_FLOAT

Format code for IEEE floating-point audio.


.. data:: WAVE_FORMAT_EXTENSIBLE

Format code for WAVE extensible headers.


.. _wave-read-objects:

Wave_read Objects
Expand Down Expand Up @@ -98,6 +118,14 @@ Wave_read Objects
Returns number of audio frames.


.. method:: getformat()

Returns the frame format code.

This is one of :data:`WAVE_FORMAT_PCM`,
:data:`WAVE_FORMAT_IEEE_FLOAT`, or :data:`WAVE_FORMAT_EXTENSIBLE`.


.. method:: getcomptype()

Returns compression type (``'NONE'`` is the only supported type).
Expand All @@ -112,8 +140,8 @@ Wave_read Objects
.. method:: getparams()

Returns a :func:`~collections.namedtuple` ``(nchannels, sampwidth,
framerate, nframes, comptype, compname)``, equivalent to output of the
``get*()`` methods.
framerate, nframes, comptype, compname)``, equivalent to output
of the ``get*()`` methods.


.. method:: readframes(n)
Expand Down Expand Up @@ -190,6 +218,9 @@ Wave_write Objects

Set the sample width to *n* bytes.

For :data:`WAVE_FORMAT_IEEE_FLOAT`, only 4-byte (32-bit) and
8-byte (64-bit) sample widths are supported.


.. method:: getsampwidth()

Expand Down Expand Up @@ -238,11 +269,32 @@ Wave_write Objects
Return the human-readable compression type name.


.. method:: setformat(format)

Set the frame format code.

Supported values are :data:`WAVE_FORMAT_PCM` and
:data:`WAVE_FORMAT_IEEE_FLOAT`.

When setting :data:`WAVE_FORMAT_IEEE_FLOAT`, the sample width must be
4 or 8 bytes.


.. method:: getformat()

Return the current frame format code.


.. method:: setparams(tuple)

The *tuple* should be ``(nchannels, sampwidth, framerate, nframes, comptype,
compname)``, with values valid for the ``set*()`` methods. Sets all
parameters.
The *tuple* should be
``(nchannels, sampwidth, framerate, nframes, comptype, compname, format)``,
with values valid for the ``set*()`` methods. Sets all parameters.

For backwards compatibility, a 6-item tuple without *format* is also
accepted and defaults to :data:`WAVE_FORMAT_PCM`.

For ``format=WAVE_FORMAT_IEEE_FLOAT``, *sampwidth* must be 4 or 8.


.. method:: getparams()
Expand Down Expand Up @@ -279,3 +331,6 @@ Wave_write Objects
Note that it is invalid to set any parameters after calling :meth:`writeframes`
or :meth:`writeframesraw`, and any attempt to do so will raise
:exc:`wave.Error`.

For :data:`WAVE_FORMAT_IEEE_FLOAT` output, a ``fact`` chunk is written as
required by the WAVE specification for non-PCM formats.
Loading
Loading