libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_lockmgr.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * @stability stable
4 * Use of this source code is governed by the ISC License.
5 *
6 * src/inc/xtc_lockmgr.h
7 * Heavyweight lock manager: per-object queue locks with
8 * deadlock detection and a 9-mode intent (RIW) lattice.
9 *
10 * The conflict matrix is configurable at create-time so
11 * callers can supply their own (e.g. a 5-mode subset, a 5-mode
12 * concurrent-data-store set, or an 8-mode set).
13 *
14 * Default 9x9 conflict matrix (1 = conflict, 0 = compatible),
15 * verbatim from libdb's `db_riw_conflicts`:
16 *
17 * NL S X WT IX IS IWR RU WW
18 * NL 0 0 0 0 0 0 0 0 0
19 * S 0 0 1 0 1 0 1 0 1
20 * X 0 1 1 1 1 1 1 1 1
21 * WT 0 0 0 0 0 0 0 0 0 (waiter placeholder)
22 * IX 0 1 1 0 0 0 0 1 1
23 * IS 0 0 1 0 0 0 0 0 1
24 * IWR 0 1 1 0 0 0 0 1 1
25 * RU 0 0 1 0 1 0 1 0 0 (read-uncommitted)
26 * WW 0 1 1 0 1 1 1 0 1 (was-written)
27 *
28 * Mode encyclopedia:
29 * NL -- no lock granted (placeholder)
30 * S -- shared / read
31 * X -- exclusive / write
32 * WT -- wait placeholder; not granted, doesn't block
33 * IX -- intent exclusive (will lock children with X)
34 * IS -- intent shared
35 * IWR -- intent read+write (combo, used by index access)
36 * RU -- read-uncommitted (degree-1 isolation; sees dirty writes)
37 * WW -- was-written (released X but txn not committed; blocks
38 * new readers but not other ex-writers in the same view)
39 *
40 * Deadlock detection runs periodically by default; callers can
41 * also force a check or configure on-every-conflict mode. Six
42 * victim-selection policies are configurable.
43 */
44
45#ifndef XTC_LOCKMGR_H
46#define XTC_LOCKMGR_H
47
48#include "xtc_export.h"
49
50#include <stddef.h>
51#include <stdint.h>
52
53#include "xtc.h"
54
55typedef struct xtc_lockmgr xtc_lockmgr_t;
56typedef uint64_t xtc_locker_t; /* opaque locker ID */
57
58typedef enum xtc_lock_mode {
59 XTC_LOCK_NL = 0, /* not granted / no lock */
60 XTC_LOCK_S = 1, /* shared / read */
61 XTC_LOCK_X = 2, /* exclusive / write */
62 XTC_LOCK_WAIT = 3, /* wait placeholder */
63 XTC_LOCK_IX = 4, /* intent exclusive */
64 XTC_LOCK_IS = 5, /* intent shared */
65 XTC_LOCK_IWR = 6, /* intent read+write */
66 XTC_LOCK_RU = 7, /* read-uncommitted / degree-1 */
67 XTC_LOCK_WW = 8, /* was-written downgrade state */
68 XTC_LOCK_NMODES = 9
69} xtc_lock_mode_t;
70
71typedef enum xtc_lock_victim_policy {
72 XTC_LOCK_VICTIM_DEFAULT = 0, /* alias for RANDOM (the default) */
73 XTC_LOCK_VICTIM_RANDOM = 1,
74 XTC_LOCK_VICTIM_OLDEST = 2,
75 XTC_LOCK_VICTIM_YOUNGEST = 3,
76 XTC_LOCK_VICTIM_MIN_LOCKS = 4,
77 XTC_LOCK_VICTIM_MAX_LOCKS = 5,
78 XTC_LOCK_VICTIM_MIN_WRITE = 6,
79 XTC_LOCK_VICTIM_MAX_WRITE = 7,
80 XTC_LOCK_VICTIM_EXPIRE = 8, /* only victims with expired timeouts */
81 XTC_LOCK_VICTIM_CUSTOM = 9 /* opts.victim_pick_fn supplies the choice */
82} xtc_lock_victim_policy_t;
83
84/* Custom victim-picker. Called when victim policy is
85 * XTC_LOCK_VICTIM_CUSTOM and the deadlock detector finds a cycle.
86 * The implementation receives the locker IDs participating in the
87 * cycle (length n_candidates >= 2) and must return an index in
88 * [0, n_candidates). May call into xtc_proc / xtc_send / xtc_recv
89 * during the choice -- e.g. to consult an external decision oracle
90 * or a randomness producer. Must not block indefinitely. */
91typedef int (*xtc_lock_victim_pick_fn)(const uint64_t *candidate_lockers,
92 int n_candidates,
93 void *user);
94
95typedef enum xtc_lock_detect_mode {
96 XTC_LOCK_DETECT_PERIODIC = 0, /* background thread runs every period_ns */
97 XTC_LOCK_DETECT_ON_BLOCK = 1, /* every conflict triggers detector synchronously */
98 XTC_LOCK_DETECT_NONE = 2 /* no detection; caller must drive */
99} xtc_lock_detect_mode_t;
100
101typedef struct xtc_lockmgr_opts {
102 int n_partitions; /* hash buckets; 0 = default 64 */
103 int64_t detect_interval_ns; /* default 100 ms (PERIODIC mode) */
104 xtc_lock_victim_policy_t victim;
105 xtc_lock_detect_mode_t detect_mode;
106
107 /* Custom victim picker. Used only when victim ==
108 * XTC_LOCK_VICTIM_CUSTOM. May be NULL otherwise. */
109 xtc_lock_victim_pick_fn victim_pick_fn;
110 void *victim_pick_user;
111
112 /* Optional custom conflict matrix. If NULL, the 9-mode RIW
113 * default above is used. If non-NULL, must be n_modes*n_modes
114 * bytes (row-major, [held*n_modes + requested]). */
115 const uint8_t *conflicts;
116 int n_modes; /* 0 = default 9 */
118
119#define XTC_LOCKMGR_OPTS_DEFAULT { \
120 .n_partitions = 64, \
121 .detect_interval_ns = 100LL * 1000 * 1000, \
122 .victim = XTC_LOCK_VICTIM_DEFAULT, \
123 .detect_mode = XTC_LOCK_DETECT_PERIODIC, \
124 .victim_pick_fn = NULL, \
125 .victim_pick_user = NULL, \
126 .conflicts = NULL, \
127 .n_modes = 0 \
128}
129
130/* Lock-vec compound op kinds. */
131typedef enum xtc_lock_op {
132 XTC_LOCK_OP_GET = 0,
133 XTC_LOCK_OP_PUT = 1,
134 XTC_LOCK_OP_PUT_ALL = 2, /* release_all for the locker; obj ignored */
135 XTC_LOCK_OP_UPGRADE = 3, /* atomic mode upgrade (caller must already hold) */
136 XTC_LOCK_OP_DOWNGRADE = 4
137} xtc_lock_op_t;
138
139typedef struct xtc_lock_req {
140 xtc_lock_op_t op;
141 xtc_lock_mode_t mode;
142 const void *obj;
143 size_t obj_size;
144 int64_t timeout_ns; /* ignored for non-GET ops */
146
147typedef struct xtc_lockmgr_stat {
148 int n_held; /* total granted entries */
149 int n_waiting; /* total waiters */
150 int n_objects; /* live lock objects */
151 int n_lockers; /* allocated locker IDs */
152 uint64_t n_acquires; /* lifetime acquire count */
153 uint64_t n_releases;
154 uint64_t n_deadlocks_found; /* victim-aborts performed */
155 uint64_t n_timeouts; /* timed-out waits */
157
158/*
159 * PUBLIC: int xtc_lockmgr_create __P((const xtc_lockmgr_opts_t *, xtc_lockmgr_t **));
160 * PUBLIC: void xtc_lockmgr_destroy __P((xtc_lockmgr_t *));
161 *
162 * PUBLIC: int xtc_lockmgr_id __P((xtc_lockmgr_t *, xtc_locker_t *));
163 * PUBLIC: int xtc_lockmgr_id_free __P((xtc_lockmgr_t *, xtc_locker_t));
164 * PUBLIC: int xtc_lockmgr_id_set_timeout __P((xtc_lockmgr_t *, xtc_locker_t, int64_t));
165 *
166 * PUBLIC: int xtc_lock_get __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t, xtc_lock_mode_t, int64_t));
167 * PUBLIC: int xtc_lock_put __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t));
168 * PUBLIC: int xtc_lock_release_all __P((xtc_lockmgr_t *, xtc_locker_t));
169 * PUBLIC: int xtc_lock_upgrade __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t, xtc_lock_mode_t));
170 * PUBLIC: int xtc_lock_downgrade __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t, xtc_lock_mode_t));
171 * PUBLIC: int xtc_lock_vec __P((xtc_lockmgr_t *, xtc_locker_t, xtc_lock_req_t *, int, int *));
172 *
173 * PUBLIC: int xtc_lockmgr_check_deadlocks __P((xtc_lockmgr_t *, int *));
174 * PUBLIC: int xtc_lockmgr_failchk __P((xtc_lockmgr_t *, xtc_locker_t));
175 * PUBLIC: int xtc_lockmgr_stat __P((const xtc_lockmgr_t *, xtc_lockmgr_stat_t *));
176 * PUBLIC: int xtc_lockmgr_n_held __P((const xtc_lockmgr_t *));
177 * PUBLIC: int xtc_lockmgr_n_waiting __P((const xtc_lockmgr_t *));
178 */
179
180XTC_API int xtc_lockmgr_create(const xtc_lockmgr_opts_t *opts, xtc_lockmgr_t **out);
181XTC_API void xtc_lockmgr_destroy(xtc_lockmgr_t *mgr);
182
183XTC_API int xtc_lockmgr_id(xtc_lockmgr_t *mgr, xtc_locker_t *out);
184XTC_API int xtc_lockmgr_id_free(xtc_lockmgr_t *mgr, xtc_locker_t l);
185
186/* Set a deadline for this locker. When DETECT_PERIODIC fires, lockers
187 * past their timeout are considered "expired" and get higher victim
188 * priority under VICTIM_EXPIRE policy. timeout_ns < 0 = no expiry. */
189XTC_API int xtc_lockmgr_id_set_timeout(xtc_lockmgr_t *mgr, xtc_locker_t l,
190 int64_t timeout_ns);
191
192/* Single acquire. timeout_ns: -1 = forever, 0 = NOWAIT. Returns
193 * XTC_OK / XTC_E_AGAIN / XTC_E_DEADLK / XTC_E_INVAL / XTC_E_NOMEM.
194 *
195 * A locker that already holds a lock on obj and asks for another mode
196 * (a CONVERSION) is checked against every OTHER holder, exactly like a
197 * fresh request; a mode its held one already dominates is a no-op; a
198 * dominating mode raises the held lock in place; any other mode is
199 * granted as an additional lock if compatible. A conversion is never
200 * queued behind waiters (they may be waiting for this locker). Before
201 * 1.50 a conversion was checked only against the locker's own mode --
202 * granting past a conflicting holder -- and could silently WEAKEN the
203 * held lock (S re-requested as IS became IS). */
204XTC_API int xtc_lock_get(xtc_lockmgr_t *mgr, xtc_locker_t locker,
205 const void *obj, size_t obj_size,
206 xtc_lock_mode_t mode, int64_t timeout_ns);
207
208XTC_API int xtc_lock_put(xtc_lockmgr_t *mgr, xtc_locker_t locker,
209 const void *obj, size_t obj_size);
210
211XTC_API int xtc_lock_release_all(xtc_lockmgr_t *mgr, xtc_locker_t locker);
212
213/* Atomic mode change on a held lock. "Stronger" and "weaker" come from
214 * the conflict MATRIX, not the enum's numeric order: new_mode must
215 * dominate (upgrade) or be dominated by (downgrade) the held mode --
216 * exclude at least / at most what it excludes -- else XTC_E_INVAL.
217 * Upgrade waits if another holder conflicts. The API has no timeout
218 * argument, so the wait is bounded by the locker's deadline
219 * (xtc_lockmgr_id_set_timeout): past it, XTC_E_AGAIN; with none it waits
220 * until granted or chosen as a deadlock victim (XTC_E_DEADLK). A
221 * granted upgrade replaces the held lock (one xtc_lock_put releases it).
222 * Downgrade is always non-blocking and promotes any waiters that the new
223 * mode no longer conflicts with. Before 1.50 both used enum order (so
224 * IX -> IS was refused and IX -> S allowed), and upgrade ignored the
225 * deadline and left the old lock held beside the new one. */
226XTC_API int xtc_lock_upgrade(xtc_lockmgr_t *mgr, xtc_locker_t locker,
227 const void *obj, size_t obj_size,
228 xtc_lock_mode_t new_mode);
229XTC_API int xtc_lock_downgrade(xtc_lockmgr_t *mgr, xtc_locker_t locker,
230 const void *obj, size_t obj_size,
231 xtc_lock_mode_t new_mode);
232
233/* Compound request, ALL-OR-NONE for acquires. Every op is validated
234 * first (a bad op / NULL obj / zero obj_size / un-GETable mode returns
235 * XTC_E_INVAL with nothing executed), then the ops run in order.
236 *
237 * On success returns XTC_OK and *out_executed == n_reqs.
238 *
239 * If an op fails (a GET that would block past its timeout_ns ->
240 * XTC_E_AGAIN, deadlock victim -> XTC_E_DEADLK, XTC_E_NOMEM, or a
241 * PUT/UPGRADE/DOWNGRADE that is rejected) the call ROLLS BACK: every
242 * GET this call performed AFTER the last non-GET op is undone, newest
243 * first -- a newly granted lock is released, a mode this call raised on
244 * a lock the locker already held is lowered back -- and the failing
245 * op's rc is returned. So a vector of only GETs leaves NO partial
246 * state and *out_executed == 0.
247 *
248 * PUT, PUT_ALL, UPGRADE and DOWNGRADE are NOT reversible (a released
249 * lock may already have been granted to another locker), so each is a
250 * rollback barrier: it and everything before it stay applied, and
251 * *out_executed is the length of that retained prefix (the index of the
252 * op after the last barrier that ran). Put the releases/mode changes
253 * FIRST and the GETs after them if you need the GETs to be atomic.
254 * A deadlock victim has had ALL its locks released by the detector,
255 * including ones held before the call; *out_executed still reports the
256 * retained prefix, but nothing is held. */
257XTC_API int xtc_lock_vec(xtc_lockmgr_t *mgr, xtc_locker_t locker,
258 xtc_lock_req_t *reqs, int n_reqs, int *out_executed);
259
260XTC_API int xtc_lockmgr_check_deadlocks(xtc_lockmgr_t *mgr, int *n_aborted);
261
262/* Mark a locker as "failed" (typically because the owning thread
263 * died). Releases all its locks and aborts any waits. */
264XTC_API int xtc_lockmgr_failchk(xtc_lockmgr_t *mgr, xtc_locker_t locker);
265
266XTC_API int xtc_lockmgr_stat(const xtc_lockmgr_t *mgr, xtc_lockmgr_stat_t *out);
267XTC_API int xtc_lockmgr_n_held(const xtc_lockmgr_t *mgr);
268XTC_API int xtc_lockmgr_n_waiting(const xtc_lockmgr_t *mgr);
269
270#endif /* XTC_LOCKMGR_H */