xtc_lockmgr(3)
---xtc_lockmgr(3)
transactional lock manager with deadlock detection
| XTC_LOCKMGR(3) | Library Functions Manual | XTC_LOCKMGR(3) |
NAME
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
SYNOPSIS
#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);
DESCRIPTION
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_PERIODICruns a background thread; theXTC_LOCK_DETECT_ON_BLOCKmode 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_CUSTOMwhich delegates to a caller-supplied callback. - Compound acquires via
xtc_lock_vec() (a vector of GETs succeeds or rolls back). - Per-locker timeouts and statistics.
xtc_lockmgr_create()
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.
xtc_lockmgr_id()
allocates a locker handle (think transaction id). Hold one per in-flight
transaction; passing it to xtc_lock_get() identifies
which transaction is asking.
xtc_lock_get()
acquires mode on the named object. Returns:
XTC_OK- The lock is held by locker.
XTC_E_AGAIN- The timeout_ns elapsed without acquiring.
XTC_E_DEADLK- The deadlock detector chose locker as a victim; the call rolls back any partial state.
XTC_E_ABORTED- The calling fiber was killed
(
xtc_exit_pid()) while it waited, and an enclosing cancellation mask of the caller defers the kill (xtc_mask_enter(),xtc_uncancelable()). 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. XTC_E_INVAL- Bad arguments or unknown locker.
xtc_lock_put()
releases the named lock. Order of release does not matter; the lockmgr
tracks per-locker held lists.
xtc_lockmgr_id_set_timeout()
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.
xtc_lock_release_all()
drops every lock held by locker in one call, the
normal end-of-transaction sweep.
xtc_lock_upgrade()
and
xtc_lock_downgrade()
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
xtc_lock_get()
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
xtc_lock_upgrade()
and
xtc_lock_downgrade(),
refusing IX to IS and allowing IX to S; upgrade ignored the locker deadline
and left the old lock held beside the new one.
xtc_lock_vec()
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.
xtc_lockmgr_check_deadlocks()
runs the cycle-detection algorithm synchronously and aborts any victims it
finds. Useful in XTC_LOCK_DETECT_NONE mode where the
caller drives detection.
xtc_lockmgr_stat()
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).
xtc_lockmgr_failchk()
sweeps for crashed lockers (those whose owning thread has exited without
releasing) and reclaims their locks.
xtc_lockmgr_n_held()
and
xtc_lockmgr_n_waiting()
return the current number of granted and blocked lock requests respectively
-- lightweight gauges for monitoring.
CHOOSING vs. xtc_lwlock / xtc_lrlock
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.
CUSTOM VICTIM PICKER
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.
SEE ALSO
HISTORY
Modeled on Berkeley DB's lock_*.c subsystem. Appeared in xtc 0.1.
| May 28, 2026 | Debian |