Skip to content

Commit 8c202f0

Browse files
committed
gh-125053: Document that ob_mutex must only be used via critical section API
Add a warning in the free-threading extensions howto explaining that PyObject.ob_mutex is reserved for the critical section API and must not be locked directly with PyMutex_Lock, as this can cause deadlocks. Extension authors who need their own lock should add a separate PyMutex field to their object struct. Also add an ob_mutex member entry under PyObject in the C API reference (Doc/c-api/structures.rst) with a cross-reference to the howto.
1 parent d736349 commit 8c202f0

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

Doc/c-api/structures.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ under :ref:`reference counting <countingrefs>`.
4848
Do not use this field directly; use :c:macro:`Py_TYPE` and
4949
:c:func:`Py_SET_TYPE` instead.
5050

51+
.. c:member:: PyMutex ob_mutex
52+
53+
A per-object lock, present only in the :term:`free-threaded <free threading>`
54+
build (when :c:macro:`Py_GIL_DISABLED` is defined).
55+
56+
This field is **reserved for use by the critical section API**
57+
(:c:macro:`Py_BEGIN_CRITICAL_SECTION` / :c:macro:`Py_END_CRITICAL_SECTION`).
58+
Do **not** lock it directly with ``PyMutex_Lock``; doing so can cause
59+
deadlocks. If you need your own lock, add a separate :c:type:`PyMutex`
60+
field to your object struct.
61+
62+
See :ref:`per-object-locks` in the free-threading extension guide for
63+
details.
64+
65+
.. versionadded:: 3.13
66+
5167

5268
.. c:type:: PyVarObject
5369

Doc/howto/free-threading-extensions.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,55 @@ Important Considerations
384384
internal extension state, standard mutexes or other synchronization
385385
primitives might be more appropriate.
386386

387+
.. _per-object-locks:
388+
389+
Per-Object Locks (``ob_mutex``)
390+
...............................
391+
392+
In the free-threaded build, each Python object contains a :c:member:`~PyObject.ob_mutex`
393+
field of type :c:type:`PyMutex`. This mutex is **reserved for use by the
394+
critical section API** (:c:macro:`Py_BEGIN_CRITICAL_SECTION` /
395+
:c:macro:`Py_END_CRITICAL_SECTION`).
396+
397+
.. warning::
398+
399+
Do **not** lock ``ob_mutex`` directly with ``PyMutex_Lock(&obj->ob_mutex)``.
400+
Mixing direct ``PyMutex_Lock`` calls with the critical section API on the
401+
same mutex can cause deadlocks, because the critical section implementation
402+
may suspend and release its locks when contention is detected.
403+
404+
Even if your own code never uses critical sections on a particular object type,
405+
**CPython internals may use the critical section API on any Python object**.
406+
For example, the garbage collector or other interpreter internals may enter a
407+
critical section on your object. If your code holds ``ob_mutex`` directly at
408+
that point, a deadlock can occur.
409+
410+
If your extension type needs its own lock, add a separate :c:type:`PyMutex`
411+
field (or another synchronization primitive) to your object struct.
412+
:c:type:`PyMutex` is very lightweight — it is only one byte — so there is
413+
negligible cost to having an additional one::
414+
415+
/* WRONG — do not lock ob_mutex directly */
416+
PyMutex_Lock(&obj->ob_mutex);
417+
...
418+
PyMutex_Unlock(&obj->ob_mutex);
419+
420+
/* RIGHT — use critical sections for ob_mutex */
421+
Py_BEGIN_CRITICAL_SECTION(obj);
422+
...
423+
Py_END_CRITICAL_SECTION();
424+
425+
/* RIGHT — use your own mutex for your own state */
426+
typedef struct {
427+
PyObject_HEAD
428+
PyMutex my_mutex; /* separate lock for extension state */
429+
int my_data;
430+
} MyObject;
431+
432+
PyMutex_Lock(&self->my_mutex);
433+
self->my_data++;
434+
PyMutex_Unlock(&self->my_mutex);
435+
387436

388437
Building Extensions for the Free-Threaded Build
389438
===============================================

0 commit comments

Comments
 (0)