|
| 1 | +# gh-125053: Document `ob_mutex` usage warning |
| 2 | + |
| 3 | +**Issue:** https://github.com/python/cpython/issues/125053 |
| 4 | +**Branch:** `doc-ob-mutex-warning` |
| 5 | + |
| 6 | +## Summary |
| 7 | + |
| 8 | +Added documentation warning that `PyObject.ob_mutex` must only be used via the |
| 9 | +critical section API (`Py_BEGIN_CRITICAL_SECTION` / `Py_END_CRITICAL_SECTION`). |
| 10 | +Directly locking it with `PyMutex_Lock(&obj->ob_mutex)` can cause deadlocks |
| 11 | +because the critical section implementation may also lock the same mutex on any |
| 12 | +Python object. Extension authors who need their own lock should create a |
| 13 | +separate `PyMutex` field. |
| 14 | + |
| 15 | +## Files changed |
| 16 | + |
| 17 | +### 1. `Doc/howto/free-threading-extensions.rst` |
| 18 | + |
| 19 | +**Location:** New subsection "Per-Object Locks (`ob_mutex`)" added after |
| 20 | +"Important Considerations" (after line 385), within the existing "Critical |
| 21 | +Sections" section. |
| 22 | + |
| 23 | +**What was added (49 lines):** |
| 24 | + |
| 25 | +- RST label `.. _per-object-locks:` for cross-referencing |
| 26 | +- Explanation that `ob_mutex` is a per-object `PyMutex` present in the |
| 27 | + free-threaded build, reserved for the critical section API |
| 28 | +- A `.. warning::` admonition against locking `ob_mutex` directly with |
| 29 | + `PyMutex_Lock`, explaining the deadlock risk |
| 30 | +- Note that CPython internals may use the critical section API on *any* Python |
| 31 | + object (e.g., garbage collector), so even types that don't use critical |
| 32 | + sections themselves are affected |
| 33 | +- Recommendation to add a separate `PyMutex` field (only 1 byte) for |
| 34 | + extension-specific locking needs |
| 35 | +- Code examples showing: |
| 36 | + - **WRONG:** direct `PyMutex_Lock(&obj->ob_mutex)` |
| 37 | + - **RIGHT:** `Py_BEGIN_CRITICAL_SECTION(obj)` / `Py_END_CRITICAL_SECTION()` |
| 38 | + - **RIGHT:** defining a custom `PyMutex my_mutex` field on your type struct |
| 39 | + |
| 40 | +### 2. `Doc/c-api/structures.rst` |
| 41 | + |
| 42 | +**Location:** New `.. c:member:: PyMutex ob_mutex` entry under `PyObject`, |
| 43 | +after the `ob_type` member (after line 49). |
| 44 | + |
| 45 | +**What was added (16 lines):** |
| 46 | + |
| 47 | +- Member documentation stating `ob_mutex` is a per-object lock present only in |
| 48 | + the free-threaded build (`Py_GIL_DISABLED`) |
| 49 | +- Warning that it is reserved for the critical section API and must not be |
| 50 | + locked directly |
| 51 | +- Cross-reference to `:ref:`per-object-locks`` in the free-threading howto |
| 52 | +- `.. versionadded:: 3.13` directive |
| 53 | + |
| 54 | +## Verification against source code |
| 55 | + |
| 56 | +All claims were verified against the actual CPython C headers: |
| 57 | + |
| 58 | +| Claim | Verified at | |
| 59 | +|---|---| |
| 60 | +| `ob_mutex` is a `PyMutex` field on `PyObject` | `Include/object.h:162` | |
| 61 | +| Only present in free-threaded build (`Py_GIL_DISABLED`) | `Include/object.h:126,150` — two separate `_object` struct defs gated on `#elif !defined(Py_GIL_DISABLED)` / `#else` | |
| 62 | +| Critical sections lock `ob_mutex` | `Include/internal/pycore_critical_section.h:113` — `_PyCriticalSection_BeginMutex(tstate, c, &op->ob_mutex)` | |
| 63 | +| `PyMutex` is 1 byte | `Include/cpython/pylock.h:33-35` — `typedef struct PyMutex { uint8_t _bits; } PyMutex;` | |
| 64 | +| `ob_mutex` introduced in 3.13 | Commit `6dfb8fe0236` first appears in tag `v3.13.0a2` | |
| 65 | +| Deadlock risk from mixing direct lock with critical sections | Critical section design suspends/releases its locks on contention (`Include/cpython/critical_section.h:22-25`), but a direct `PyMutex_Lock` holder does not participate in this protocol | |
| 66 | + |
| 67 | +## Build verification |
| 68 | + |
| 69 | +- `make html` in `Doc/` completed with **no warnings or errors** |
| 70 | +- `ob_mutex` appears in both generated HTML pages |
| 71 | +- Cross-reference from `structures.html` to `free-threading-extensions.html#per-object-locks` renders correctly |
0 commit comments