Robust Process-Shared Mutexes: Repair State Before Marking a Dead Owner’s Lock Consistent

When pthread_mutex_lock returns EOWNERDEAD for a robust mutex, the caller has acquired the lock and must reconcile the protected data before declaring it consistent. Calling pthread_mutex_consistent is an acknowledgment of completed repair, not the repair itself. If the invariant cannot be restored, keep ordinary consumers out and follow an explicit unrecoverable-state policy.

This guide uses Linux man-pages 6.12 and GNU libc 2.41 implementation evidence for process-shared robust mutexes. An executed host fixture used two owned anonymous shared mappings and voluntarily exiting child processes. It did not test power-loss persistence, device transactions, priority inheritance or a production shared-memory segment.

Configure sharing and robustness independently

Place the mutex in memory genuinely shared by the participating processes and initialize it exactly once before use. The process-shared attribute must be PTHREAD_PROCESS_SHARED; the default is process-private. Merely placing a default mutex inside a shared mapping does not select the correct cross-process contract.

Separately select PTHREAD_MUTEX_ROBUST using an initialized attribute object, check every returned error number, and initialize the mutex from those attributes. The robustness reference distinguishes this from the default stalled behavior, where an owner dying while holding the mutex can leave future lockers blocked.

Initialization itself needs a bootstrap protocol. A second process must not lock bytes that the creator has not finished initializing. Robustness handles an owner dying after the mutex exists and is used; it is not a safe concurrent-initialization or object-discovery mechanism.

Owner death leads to exclusive recovery ownership, invariant repair and consistency acknowledgment, or an unrecoverable terminal path; host fixture states are shown separately.
Original recovery-state model. EOWNERDEAD is an acquired-lock result requiring application reconciliation.

Decode lock results into ownership states

Lock result Ownership and next action
Zero Lock acquired in the ordinary state; validate the application’s normal invariants.
EOWNERDEAD Lock acquired, but prior owner died; enter exclusive recovery before ordinary use.
ENOTRECOVERABLE Do not access protected data as an owner; route to object-generation recovery.
Other error Handle according to its contract; do not perform an unconditional unlock.

The GNU libc 2.41 mutex implementation explicitly returns EOWNERDEAD after acquiring the robust lock and recording inconsistency. Its unrecoverable path releases the internal lock before returning ENOTRECOVERABLE. A wrapper that treats every nonzero result as “nothing acquired” leaks the recovery lock; one that unlocks after every error is also wrong.

Use a scoped ownership state that distinguishes ordinary ownership from recovery ownership. The cleanup path must know whether it owes an unlock and whether the data has been repaired. Do not erase the original return code before the caller decides which state it entered.

Repair an application invariant, not a mutex field

Define a protected-state invariant independently of the locking API. In a hypothetical shared record, fields A and B represent one approved generation and must agree. A writer changes A and dies before changing B. The next owner may see a structurally readable record that is semantically incomplete.

Recovery needs authoritative evidence: an intact previous generation, a validated journal, or enough redundant fields to determine the intended state. If the design cannot distinguish old from partially new data, a robust mutex cannot invent the answer. Mark the object unusable and require controlled reconstruction instead of choosing a plausible value.

The pthread_mutex_consistent contract makes this application responsibility explicit. Repair all dependent fields, verify their invariant while still holding the lock, then call consistent and check its result. Only afterward should the normal unlock publish the repaired state to later lockers.

Choose the unrecoverable path deliberately

If a recovery owner unlocks without making the mutex consistent, later lock attempts return ENOTRECOVERABLE. This can be the correct terminal result when data cannot be trusted. Record why repair failed and prevent normal processing from bypassing the failed lock.

Reconstruction must retire the old object generation under external coordination. Do not destroy or reinitialize the mutex while other processes can still wait on or access its storage. A new mapping and generation can be easier to reason about than resetting a live shared object, but all participants need a controlled handoff and cleanup rule.

If the recovery owner itself dies before calling consistent, subsequent lockers can receive EOWNERDEAD again. Make recovery restartable and avoid assuming it executes exactly once. A partially performed repair should leave enough evidence for another recovery owner to continue safely or reject the object.

Keep external effects outside the promise

A mutex can report that its owner died, but it does not roll back a device write, network request or durable storage update already issued by that owner. If the protected transaction includes such effects, recovery must reconcile their outcomes separately. Replaying an uncertain actuator command merely because the lock is recoverable is not justified.

Linux also reports owner-death behavior for a process-shared robust mutex when the owner executes a new program without unlocking, as documented in the robustness reference. Treat exec and shutdown as lifecycle boundaries; avoid relying on owner death as ordinary cleanup.

Executed owner-exit cases

The GCC 14.2.0 host fixture initialized a process-shared robust mutex and fields A=B=1 in an anonymous MAP_SHARED region. Its owned child acquired the mutex, changed A to two and exited without unlocking. After reaping that child, the parent acquired with EOWNERDEAD and verified the inconsistent pair A=2, B=1.

In the repair case, the parent restored A=B=1, called consistent, unlocked and successfully reacquired normally. A fresh second case instead unlocked without consistent; its next acquisition returned ENOTRECOVERABLE. Both cases then destroyed their retired mutex and unmapped the owned region. No forced signal or unrelated process was involved.

Those results demonstrate the host state transitions on x86-64 Linux 6.18.44. Proposed target tests add death at every protected mutation boundary, a recovery owner dying mid-repair, incompatible shared-layout versions and peers that fail to retire an old generation. The acceptance artifact is an invariant-specific recovery ledger with explicit ownership on every return path, rather than a generic “robust mutex enabled” setting.

Similar Posts