xtc_lockmgr(3)

---

xtc_lockmgr(3)

transactional lock manager with deadlock detection

XTC_LOCKMGR(3) Library Functions Manual XTC_LOCKMGR(3)

xtc_lockmgr_create, xtc_lockmgr_destroy, xtc_lockmgr_id, xtc_lockmgr_id_free, xtc_lockmgr_id_set_timeout, xtc_lock_get, xtc_lock_put, xtc_lock_release_all, xtc_lock_upgrade, xtc_lock_downgrade, xtc_lock_vec, xtc_lockmgr_check_deadlocks, xtc_lockmgr_stat, xtc_lockmgr_n_held, xtc_lockmgr_n_waiting, xtc_lockmgr_failchk — transactional lock manager with deadlock detection

#include <xtc.h>
#include <xtc_lockmgr.h>

int
xtc_lockmgr_create(const xtc_lockmgr_opts_t *opts, xtc_lockmgr_t **out);

void
xtc_lockmgr_destroy(xtc_lockmgr_t *m);

int
xtc_lockmgr_id(xtc_lockmgr_t *m, xtc_locker_t *out);

int
xtc_lockmgr_id_free(xtc_lockmgr_t *m, xtc_locker_t locker);

int
xtc_lockmgr_id_set_timeout(xtc_lockmgr_t *m, xtc_locker_t l, int64_t timeout_ns);

int
xtc_lock_get(xtc_lockmgr_t *m, xtc_locker_t locker, const void *key, size_t key_size, xtc_lock_mode_t mode, int64_t timeout_ns);

int
xtc_lock_put(xtc_lockmgr_t *m, xtc_locker_t locker, const void *key, size_t key_size);

int
xtc_lock_release_all(xtc_lockmgr_t *m, xtc_locker_t locker);

int
xtc_lock_upgrade(xtc_lockmgr_t *m, xtc_locker_t locker, const void *obj, size_t obj_size, xtc_lock_mode_t new_mode);

int
xtc_lock_downgrade(xtc_lockmgr_t *m, xtc_locker_t locker, const void *obj, size_t obj_size, xtc_lock_mode_t new_mode);

int
xtc_lock_vec(xtc_lockmgr_t *m, xtc_locker_t locker, xtc_lock_req_t *reqs, int n_reqs, int *out_executed);

int
xtc_lockmgr_check_deadlocks(xtc_lockmgr_t *m, int *n_aborted);

int
xtc_lockmgr_stat(xtc_lockmgr_t *m, xtc_lockmgr_stat_t *out);

int
xtc_lockmgr_n_held(const xtc_lockmgr_t *m);

int
xtc_lockmgr_n_waiting(const xtc_lockmgr_t *m);

int
xtc_lockmgr_failchk(xtc_lockmgr_t *m);

xtc_lockmgr is a transactional lock manager modeled on Berkeley DB's lock_*.c subsystem. It provides:

  • Named lockable objects identified by arbitrary (key, key_size) byte strings.
  • Nine lock modes via the BDB ‘read-intent-write’ matrix (XTC_LOCK_NL, XTC_LOCK_S, XTC_LOCK_X, XTC_LOCK_WAIT, XTC_LOCK_IS, XTC_LOCK_IX, XTC_LOCK_IWR, XTC_LOCK_RU, XTC_LOCK_WW), or a caller-supplied conflict matrix.
  • Deadlock detection in two flavours: XTC_LOCK_DETECT_PERIODIC runs a background thread; the XTC_LOCK_DETECT_ON_BLOCK mode runs synchronously every time a requestor would block.
  • Eight victim policies (XTC_LOCK_VICTIM_RANDOM, OLDEST, YOUNGEST, MIN_LOCKS, MAX_LOCKS, MIN_WRITE, MAX_WRITE, EXPIRE) plus XTC_LOCK_VICTIM_CUSTOM which delegates to a caller-supplied callback.
  • Compound acquires via () (a vector of GETs succeeds or rolls back).
  • Per-locker timeouts and statistics.

() opens a lock manager with the given options. Options control partition count (for hash sharding), detector mode and interval, victim policy, and the conflict matrix.

() allocates a locker handle (think transaction id). Hold one per in-flight transaction; passing it to xtc_lock_get() identifies which transaction is asking.

() acquires mode on the named object. Returns:

The lock is held by locker.
The timeout_ns elapsed without acquiring.
The deadlock detector chose locker as a victim; the call rolls back any partial state.
The calling fiber was killed (()) while it waited, and an enclosing cancellation mask of the caller defers the kill ((), ()). The wait is abandoned and the lock is NOT held; the kill is delivered when the caller's mask drops. Without an enclosing mask the kill is delivered at once and the call does not return.
Bad arguments or unknown locker.

() releases the named lock. Order of release does not matter; the lockmgr tracks per-locker held lists.

() sets a per-locker deadline: once l has held its locks past timeout_ns, the periodic detector treats it as expired for victim selection under the EXPIRE policy. A negative value disables expiry.

() drops every lock held by locker in one call, the normal end-of-transaction sweep.

() and () atomically change the mode of a lock already held on obj to new_mode. Stronger and weaker are defined by the conflict matrix, not by the numeric order of the mode enum: an upgrade's new_mode must exclude at least everything the held mode excludes, a downgrade's at most; otherwise the call returns XTC_E_INVAL. Upgrade waits while another holder conflicts. Having no timeout argument, it is bounded by the locker's deadline (xtc_lockmgr_id_set_timeout()): past it the call returns XTC_E_AGAIN; with no deadline it waits until granted or chosen as a deadlock victim. A granted upgrade replaces the held lock, so one xtc_lock_put() releases it. Downgrade never blocks and promotes any waiters the weaker mode no longer conflicts with.

Calling () for an object the locker already holds is a conversion. It is checked against every other holder like a fresh request; a mode the held one already covers is a no-op, and a stronger one raises the held lock in place. A conversion is never queued behind waiters, which may be waiting for the same locker.

Releases before 1.50 checked a conversion only against the locker's own mode, so it could be granted beside a conflicting holder, and could weaken the held lock (an S holder asking for IS was moved to IS). They also ordered modes by enum value in () and (), refusing IX to IS and allowing IX to S; upgrade ignored the locker deadline and left the old lock held beside the new one.

() runs a vector of n_reqs requests (GET, PUT, PUT_ALL, UPGRADE, DOWNGRADE) for locker. Every request is validated first; a bad op, NULL obj, zero obj_size or un-GETable mode returns XTC_E_INVAL with nothing executed. On success it returns XTC_OK with *out_executed == n_reqs. If a request fails (a GET that would block past its timeout_ns returns XTC_E_AGAIN, a deadlock victim XTC_E_DEADLK, or a rejected PUT/UPGRADE/DOWNGRADE) the call rolls back: every GET it performed after the last non-GET request is undone, newest first (a newly granted lock is released; a mode the call raised on a lock locker already held is lowered back), and the failing request's code is returned. A vector of only GETs therefore leaves no partial state, and *out_executed is 0.

PUT, PUT_ALL, UPGRADE and DOWNGRADE cannot be undone (a released lock may already be granted to another locker), so each is a rollback barrier: it and every request before it stay applied, and *out_executed reports the length of that retained prefix. Order releases and mode changes first and GETs after them when the GETs must be all-or-none. A deadlock victim has had all of its locks released by the detector, including locks held before the call.

() runs the cycle-detection algorithm synchronously and aborts any victims it finds. Useful in XTC_LOCK_DETECT_NONE mode where the caller drives detection.

() fills out with current counts: held, waiting, total acquires, total releases, deadlocks found, victims aborted, etc. No locks held during the snapshot (counters are atomic).

() sweeps for crashed lockers (those whose owning thread has exited without releasing) and reclaims their locks.

() and () return the current number of granted and blocked lock requests respectively -- lightweight gauges for monitoring.

Use xtc_lockmgr when:

  • Locks are named by user-supplied keys (e.g. table name, page id) rather than living in a fixed location.
  • You need multiple lock modes beyond shared / exclusive (intent locks for hierarchical locking).
  • You want deadlock detection out of the box.
  • Compound acquires must be atomic.
For everything else, prefer xtc_lwlock(3) or xtc_lrlock(3).

Setting opts.victim to XTC_LOCK_VICTIM_CUSTOM and providing opts.victim_pick_fn delegates the choice to user code:

typedef int (*xtc_lock_victim_pick_fn)(
    const uint64_t *candidate_lockers, int n_candidates,
    void *user);

The callback receives the locker IDs in the cycle and returns an index in [0, n_candidates]. See examples/04_lockmgr_demo.c for a deck-shuffled randomised picker.

xtc_lwlock(3), xtc_lrlock(3), xtc(7)

Modeled on Berkeley DB's lock_*.c subsystem. Appeared in xtc 0.1.

May 28, 2026 Debian

View the mdoc source