Skip to content
Open
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
100 changes: 100 additions & 0 deletions Doc/c-api/dict.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ Dictionary objects

The first argument can be a :class:`dict` or a :class:`frozendict`.

.. note::

The operation is atomic in the :term:`free-threaded build`, if *key*
is a builtin type (e.g. :class:`str`, :class:`int`, :class:`float`) or any
other object which does not define :meth:`~object.__hash__` and :meth:`~object.__eq__` methods.

.. versionchanged:: next
Also accept :class:`frozendict`.

Expand Down Expand Up @@ -105,6 +111,12 @@ Dictionary objects
``0`` on success or ``-1`` on failure. This function *does not* steal a
reference to *val*.

.. note::

The operation is atomic in the :term:`free-threaded build`, if *key*
is a builtin type (e.g. :class:`str`, :class:`int`, :class:`float`) or any
other object which does not define :meth:`~object.__hash__` and :meth:`~object.__eq__` methods.


.. c:function:: int PyDict_SetItemString(PyObject *p, const char *key, PyObject *val)

Expand All @@ -120,6 +132,12 @@ Dictionary objects
If *key* is not in the dictionary, :exc:`KeyError` is raised.
Return ``0`` on success or ``-1`` on failure.

.. note::

The operation is atomic in the :term:`free-threaded build`, if *key*
is a builtin type (e.g. :class:`str`, :class:`int`, :class:`float`) or any
other object which does not define :meth:`~object.__hash__` and :meth:`~object.__eq__` methods.


.. c:function:: int PyDict_DelItemString(PyObject *p, const char *key)

Expand All @@ -140,6 +158,12 @@ Dictionary objects

The first argument can be a :class:`dict` or a :class:`frozendict`.

.. note::

The operation is atomic in the :term:`free-threaded build`, if *key*
is a builtin type (e.g. :class:`str`, :class:`int`, :class:`float`) or any
other object which does not define :meth:`~object.__hash__` and :meth:`~object.__eq__` methods.

.. versionadded:: 3.13

.. versionchanged:: next
Expand All @@ -162,6 +186,13 @@ Dictionary objects
:meth:`~object.__eq__` methods are silently ignored.
Prefer the :c:func:`PyDict_GetItemWithError` function instead.

.. note::

In the :term:`free-threaded build`, the returned
:term:`borrowed reference` may become invalid if another thread modifies
the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which
returns a :term:`strong reference`.

.. versionchanged:: 3.10
Calling this API without an :term:`attached thread state` had been allowed for historical
reason. It is no longer allowed.
Expand All @@ -177,6 +208,13 @@ Dictionary objects
occurred. Return ``NULL`` **without** an exception set if the key
wasn't present.

.. note::

In the :term:`free-threaded build`, the returned
:term:`borrowed reference` may become invalid if another thread modifies
the dictionary concurrently. Prefer :c:func:`PyDict_GetItemRef`, which
returns a :term:`strong reference`.

.. versionchanged:: next
Also accept :class:`frozendict`.

Expand All @@ -195,6 +233,13 @@ Dictionary objects
Prefer using the :c:func:`PyDict_GetItemWithError` function with your own
:c:func:`PyUnicode_FromString` *key* instead.

.. note::

In the :term:`free-threaded build`, the returned
:term:`borrowed reference` may become invalid if another thread modifies
the dictionary concurrently. Prefer :c:func:`PyDict_GetItemStringRef`,
which returns a :term:`strong reference`.

.. versionchanged:: next
Also accept :class:`frozendict`.

Expand All @@ -221,6 +266,14 @@ Dictionary objects

.. versionadded:: 3.4

.. note::

In the :term:`free-threaded build`, the returned
:term:`borrowed reference` may become invalid if another thread modifies
the dictionary concurrently. Prefer :c:func:`PyDict_SetDefaultRef`,
which returns a :term:`strong reference`.



.. c:function:: int PyDict_SetDefaultRef(PyObject *p, PyObject *key, PyObject *default_value, PyObject **result)

Expand All @@ -240,6 +293,12 @@ Dictionary objects
These may refer to the same object: in that case you hold two separate
references to it.

.. note::

The operation is atomic in the :term:`free-threaded build`, if *key*
is a builtin type (e.g. :class:`str`, :class:`int`, :class:`float`) or any
other object which does not define :meth:`~object.__hash__` and :meth:`~object.__eq__` methods.

.. versionadded:: 3.13


Expand All @@ -257,6 +316,12 @@ Dictionary objects
Similar to :meth:`dict.pop`, but without the default value and
not raising :exc:`KeyError` if the key is missing.

.. note::

The operation is atomic in the :term:`free-threaded build`, if *key*
is a builtin type (e.g. :class:`str`, :class:`int`, :class:`float`) or any
other object which does not define :meth:`~object.__hash__` and :meth:`~object.__eq__` methods.

.. versionadded:: 3.13


Expand Down Expand Up @@ -403,6 +468,13 @@ Dictionary objects
only be added if there is not a matching key in *a*. Return ``0`` on
success or ``-1`` if an exception was raised.

.. note::

In the :term:`free-threaded build`, when *b* is a
:class:`dict` (with the standard iterator), both *a* and *b* are locked
for the duration of the operation. When *b* is a non-dict mapping, only
*a* is locked; *b* may be concurrently modified by another thread.


.. c:function:: int PyDict_Update(PyObject *a, PyObject *b)

Expand All @@ -412,6 +484,13 @@ Dictionary objects
argument has no "keys" attribute. Return ``0`` on success or ``-1`` if an
exception was raised.

.. note::

In the :term:`free-threaded build`, when *b* is a
:class:`dict` (with the standard iterator), both *a* and *b* are locked
for the duration of the operation. When *b* is a non-dict mapping, only
*a* is locked; *b* may be concurrently modified by another thread.


.. c:function:: int PyDict_MergeFromSeq2(PyObject *a, PyObject *seq2, int override)

Expand All @@ -427,13 +506,27 @@ Dictionary objects
if override or key not in a:
a[key] = value

.. note::

In the :term:`free-threaded <free threading>` build, only *a* is locked.
The iteration over *seq2* is not synchronized; *seq2* may be concurrently
modified by another thread.


.. c:function:: int PyDict_AddWatcher(PyDict_WatchCallback callback)

Register *callback* as a dictionary watcher. Return a non-negative integer
id which must be passed to future calls to :c:func:`PyDict_Watch`. In case
of error (e.g. no more watcher IDs available), return ``-1`` and set an
exception.

.. note::

This function is not internally synchronized. In the
:term:`free-threaded <free threading>` build, callers should ensure no
concurrent calls to :c:func:`PyDict_AddWatcher` or
:c:func:`PyDict_ClearWatcher` are in progress.

.. versionadded:: 3.12

.. c:function:: int PyDict_ClearWatcher(int watcher_id)
Expand All @@ -442,6 +535,13 @@ Dictionary objects
:c:func:`PyDict_AddWatcher`. Return ``0`` on success, ``-1`` on error (e.g.
if the given *watcher_id* was never registered.)

.. note::

This function is not internally synchronized. In the
:term:`free-threaded <free threading>` build, callers should ensure no
concurrent calls to :c:func:`PyDict_AddWatcher` or
:c:func:`PyDict_ClearWatcher` are in progress.

.. versionadded:: 3.12

.. c:function:: int PyDict_Watch(int watcher_id, PyObject *dict)
Expand Down
65 changes: 63 additions & 2 deletions Doc/data/threadsafety.dat
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,71 @@
# The function name must match the C domain identifier used in the documentation.

# Synchronization primitives (Doc/c-api/synchronization.rst)
PyMutex_Lock:shared:
PyMutex_Unlock:shared:
PyMutex_Lock:atomic:
PyMutex_Unlock:atomic:
PyMutex_IsLocked:atomic:


# Dictionary objects (Doc/c-api/dict.rst)

# Type checks - read ob_type pointer, always safe
PyDict_Check:atomic:
PyDict_CheckExact:atomic:

# Creation - pure allocation, no shared state
PyDict_New:atomic:

# Lock-free lookups - use _Py_dict_lookup_threadsafe(), no locking
# atomic with simple types
PyDict_Contains:shared:
PyDict_ContainsString:atomic:
PyDict_GetItemRef:shared:
PyDict_GetItemStringRef:atomic:
PyDict_Size:atomic:
PyDict_GET_SIZE:atomic:

# Borrowed-reference lookups - lock-free dict access but returned
# borrowed reference is unsafe in free-threaded builds without
# external synchronization
PyDict_GetItem:compatible:
PyDict_GetItemWithError:compatible:
PyDict_GetItemString:compatible:
PyDict_SetDefault:compatible:

# Iteration - no locking; returns borrowed refs
PyDict_Next:compatible:

# Single-item mutations - protected by per-object critical section
PyDict_SetItem:shared:
PyDict_SetItemString:atomic:
PyDict_DelItem:shared:
PyDict_DelItemString:atomic:
PyDict_SetDefaultRef:shared:
PyDict_Pop:shared:
PyDict_PopString:atomic:

# Bulk reads - hold per-object lock for duration
PyDict_Clear:atomic:
PyDict_Copy:atomic:
PyDict_Keys:atomic:
PyDict_Values:atomic:
PyDict_Items:atomic:

# Merge/update - lock target dict; also lock source when it is a dict
PyDict_Update:shared:
PyDict_Merge:shared:
PyDict_MergeFromSeq2:shared:

# Watcher registration - no synchronization on interpreter state
PyDict_AddWatcher:compatible:
PyDict_ClearWatcher:compatible:

# Per-dict watcher tags - non-atomic RMW on _ma_watcher_tag;
# safe on distinct dicts only
PyDict_Watch:distinct:
PyDict_Unwatch:distinct:


# List objects (Doc/c-api/list.rst)

# Type checks - read ob_type pointer, always safe
Expand Down
Loading