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 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/xtc_lockmgr.h
6 * Heavyweight lock manager: per-object queue locks with
7 * deadlock detection and a 9-mode intent (RIW) lattice.
8 *
9 * The conflict matrix is configurable at create-time so
10 * callers can supply their own (e.g. a 5-mode subset, a 5-mode
11 * concurrent-data-store set, or an 8-mode set).
12 *
13 * Default 9x9 conflict matrix (1 = conflict, 0 = compatible),
14 * verbatim from libdb's `db_riw_conflicts`:
15 *
16 * NL S X WT IX IS IWR RU WW
17 * NL 0 0 0 0 0 0 0 0 0
18 * S 0 0 1 0 1 0 1 0 1
19 * X 0 1 1 1 1 1 1 1 1
20 * WT 0 0 0 0 0 0 0 0 0 (waiter placeholder)
21 * IX 0 1 1 0 0 0 0 1 1
22 * IS 0 0 1 0 0 0 0 0 1
23 * IWR 0 1 1 0 0 0 0 1 1
24 * RU 0 0 1 0 1 0 1 0 0 (read-uncommitted)
25 * WW 0 1 1 0 1 1 1 0 1 (was-written)
26 *
27 * Mode encyclopedia:
28 * NL -- no lock granted (placeholder)
29 * S -- shared / read
30 * X -- exclusive / write
31 * WT -- wait placeholder; not granted, doesn't block
32 * IX -- intent exclusive (will lock children with X)
33 * IS -- intent shared
34 * IWR -- intent read+write (combo, used by index access)
35 * RU -- read-uncommitted (degree-1 isolation; sees dirty writes)
36 * WW -- was-written (released X but txn not committed; blocks
37 * new readers but not other ex-writers in the same view)
38 *
39 * Deadlock detection runs periodically by default; callers can
40 * also force a check or configure on-every-conflict mode. Six
41 * victim-selection policies are configurable.
42 */
43
44#ifndef XTC_LOCKMGR_H
45#define XTC_LOCKMGR_H
46
47#include <stddef.h>
48#include <stdint.h>
49
50#include "xtc.h"
51
52typedef struct xtc_lockmgr xtc_lockmgr_t;
53typedef uint64_t xtc_locker_t; /* opaque locker ID */
54
55typedef enum xtc_lock_mode {
56 XTC_LOCK_NL = 0, /* not granted / no lock */
57 XTC_LOCK_S = 1, /* shared / read */
58 XTC_LOCK_X = 2, /* exclusive / write */
59 XTC_LOCK_WAIT = 3, /* wait placeholder */
60 XTC_LOCK_IX = 4, /* intent exclusive */
61 XTC_LOCK_IS = 5, /* intent shared */
62 XTC_LOCK_IWR = 6, /* intent read+write */
63 XTC_LOCK_RU = 7, /* read-uncommitted / degree-1 */
64 XTC_LOCK_WW = 8, /* was-written downgrade state */
65 XTC_LOCK_NMODES = 9
66} xtc_lock_mode_t;
67
68typedef enum xtc_lock_victim_policy {
69 XTC_LOCK_VICTIM_DEFAULT = 0, /* alias for RANDOM (the default) */
70 XTC_LOCK_VICTIM_RANDOM = 1,
71 XTC_LOCK_VICTIM_OLDEST = 2,
72 XTC_LOCK_VICTIM_YOUNGEST = 3,
73 XTC_LOCK_VICTIM_MIN_LOCKS = 4,
74 XTC_LOCK_VICTIM_MAX_LOCKS = 5,
75 XTC_LOCK_VICTIM_MIN_WRITE = 6,
76 XTC_LOCK_VICTIM_MAX_WRITE = 7,
77 XTC_LOCK_VICTIM_EXPIRE = 8, /* only victims with expired timeouts */
78 XTC_LOCK_VICTIM_CUSTOM = 9 /* opts.victim_pick_fn supplies the choice */
79} xtc_lock_victim_policy_t;
80
81/* Custom victim-picker. Called when victim policy is
82 * XTC_LOCK_VICTIM_CUSTOM and the deadlock detector finds a cycle.
83 * The implementation receives the locker IDs participating in the
84 * cycle (length n_candidates >= 2) and must return an index in
85 * [0, n_candidates). May call into xtc_proc / xtc_send / xtc_recv
86 * during the choice -- e.g. to consult an external decision oracle
87 * or a randomness producer. Must not block indefinitely. */
88typedef int (*xtc_lock_victim_pick_fn)(const uint64_t *candidate_lockers,
89 int n_candidates,
90 void *user);
91
92typedef enum xtc_lock_detect_mode {
93 XTC_LOCK_DETECT_PERIODIC = 0, /* background thread runs every period_ns */
94 XTC_LOCK_DETECT_ON_BLOCK = 1, /* every conflict triggers detector synchronously */
95 XTC_LOCK_DETECT_NONE = 2 /* no detection; caller must drive */
96} xtc_lock_detect_mode_t;
97
98typedef struct xtc_lockmgr_opts {
99 int n_partitions; /* hash buckets; 0 = default 64 */
100 int64_t detect_interval_ns; /* default 100 ms (PERIODIC mode) */
101 xtc_lock_victim_policy_t victim;
102 xtc_lock_detect_mode_t detect_mode;
103
104 /* Custom victim picker. Used only when victim ==
105 * XTC_LOCK_VICTIM_CUSTOM. May be NULL otherwise. */
106 xtc_lock_victim_pick_fn victim_pick_fn;
107 void *victim_pick_user;
108
109 /* Optional custom conflict matrix. If NULL, the 9-mode RIW
110 * default above is used. If non-NULL, must be n_modes*n_modes
111 * bytes (row-major, [held*n_modes + requested]). */
112 const uint8_t *conflicts;
113 int n_modes; /* 0 = default 9 */
115
116#define XTC_LOCKMGR_OPTS_DEFAULT { \
117 .n_partitions = 64, \
118 .detect_interval_ns = 100LL * 1000 * 1000, \
119 .victim = XTC_LOCK_VICTIM_DEFAULT, \
120 .detect_mode = XTC_LOCK_DETECT_PERIODIC, \
121 .victim_pick_fn = NULL, \
122 .victim_pick_user = NULL, \
123 .conflicts = NULL, \
124 .n_modes = 0 \
125}
126
127/* Lock-vec compound op kinds. */
128typedef enum xtc_lock_op {
129 XTC_LOCK_OP_GET = 0,
130 XTC_LOCK_OP_PUT = 1,
131 XTC_LOCK_OP_PUT_ALL = 2, /* release_all for the locker; obj ignored */
132 XTC_LOCK_OP_UPGRADE = 3, /* atomic mode upgrade (caller must already hold) */
133 XTC_LOCK_OP_DOWNGRADE = 4
134} xtc_lock_op_t;
135
136typedef struct xtc_lock_req {
137 xtc_lock_op_t op;
138 xtc_lock_mode_t mode;
139 const void *obj;
140 size_t obj_size;
141 int64_t timeout_ns; /* ignored for non-GET ops */
143
144typedef struct xtc_lockmgr_stat {
145 int n_held; /* total granted entries */
146 int n_waiting; /* total waiters */
147 int n_objects; /* live lock objects */
148 int n_lockers; /* allocated locker IDs */
149 uint64_t n_acquires; /* lifetime acquire count */
150 uint64_t n_releases;
151 uint64_t n_deadlocks_found; /* victim-aborts performed */
152 uint64_t n_timeouts; /* timed-out waits */
154
155/*
156 * PUBLIC: int xtc_lockmgr_create __P((const xtc_lockmgr_opts_t *, xtc_lockmgr_t **));
157 * PUBLIC: void xtc_lockmgr_destroy __P((xtc_lockmgr_t *));
158 *
159 * PUBLIC: int xtc_lockmgr_id __P((xtc_lockmgr_t *, xtc_locker_t *));
160 * PUBLIC: int xtc_lockmgr_id_free __P((xtc_lockmgr_t *, xtc_locker_t));
161 * PUBLIC: int xtc_lockmgr_id_set_timeout __P((xtc_lockmgr_t *, xtc_locker_t, int64_t));
162 *
163 * PUBLIC: int xtc_lock_get __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t, xtc_lock_mode_t, int64_t));
164 * PUBLIC: int xtc_lock_put __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t));
165 * PUBLIC: int xtc_lock_release_all __P((xtc_lockmgr_t *, xtc_locker_t));
166 * PUBLIC: int xtc_lock_upgrade __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t, xtc_lock_mode_t));
167 * PUBLIC: int xtc_lock_downgrade __P((xtc_lockmgr_t *, xtc_locker_t, const void *, size_t, xtc_lock_mode_t));
168 * PUBLIC: int xtc_lock_vec __P((xtc_lockmgr_t *, xtc_locker_t, xtc_lock_req_t *, int, int *));
169 *
170 * PUBLIC: int xtc_lockmgr_check_deadlocks __P((xtc_lockmgr_t *, int *));
171 * PUBLIC: int xtc_lockmgr_failchk __P((xtc_lockmgr_t *, xtc_locker_t));
172 * PUBLIC: int xtc_lockmgr_stat __P((const xtc_lockmgr_t *, xtc_lockmgr_stat_t *));
173 * PUBLIC: int xtc_lockmgr_n_held __P((const xtc_lockmgr_t *));
174 * PUBLIC: int xtc_lockmgr_n_waiting __P((const xtc_lockmgr_t *));
175 */
176
177int xtc_lockmgr_create(const xtc_lockmgr_opts_t *opts, xtc_lockmgr_t **out);
178void xtc_lockmgr_destroy(xtc_lockmgr_t *mgr);
179
180int xtc_lockmgr_id(xtc_lockmgr_t *mgr, xtc_locker_t *out);
181int xtc_lockmgr_id_free(xtc_lockmgr_t *mgr, xtc_locker_t l);
182
183/* Set a deadline for this locker. When DETECT_PERIODIC fires, lockers
184 * past their timeout are considered "expired" and get higher victim
185 * priority under VICTIM_EXPIRE policy. timeout_ns < 0 = no expiry. */
186int xtc_lockmgr_id_set_timeout(xtc_lockmgr_t *mgr, xtc_locker_t l,
187 int64_t timeout_ns);
188
189/* Single acquire. timeout_ns: -1 = forever, 0 = NOWAIT. Returns
190 * XTC_OK / XTC_E_AGAIN / XTC_E_DEADLK / XTC_E_INVAL / XTC_E_NOMEM. */
191int xtc_lock_get(xtc_lockmgr_t *mgr, xtc_locker_t locker,
192 const void *obj, size_t obj_size,
193 xtc_lock_mode_t mode, int64_t timeout_ns);
194
195int xtc_lock_put(xtc_lockmgr_t *mgr, xtc_locker_t locker,
196 const void *obj, size_t obj_size);
197
198int xtc_lock_release_all(xtc_lockmgr_t *mgr, xtc_locker_t locker);
199
200/* Atomic mode change on a held lock. Upgrade may block (returns AGAIN
201 * on NOWAIT-style timeout=0). Downgrade is always non-blocking and
202 * promotes any waiters that the new (weaker) mode no longer conflicts
203 * with. */
204int xtc_lock_upgrade(xtc_lockmgr_t *mgr, xtc_locker_t locker,
205 const void *obj, size_t obj_size,
206 xtc_lock_mode_t new_mode);
207int xtc_lock_downgrade(xtc_lockmgr_t *mgr, xtc_locker_t locker,
208 const void *obj, size_t obj_size,
209 xtc_lock_mode_t new_mode);
210
211/* Atomic compound: all ops are validated, then executed. If any
212 * GET would block in the middle, the prior GETs are NOT rolled back;
213 * set timeout_ns=0 in every req for
214 * fully-atomic semantics. *out_executed receives the number of
215 * ops that succeeded. */
216int xtc_lock_vec(xtc_lockmgr_t *mgr, xtc_locker_t locker,
217 xtc_lock_req_t *reqs, int n_reqs, int *out_executed);
218
219int xtc_lockmgr_check_deadlocks(xtc_lockmgr_t *mgr, int *n_aborted);
220
221/* Mark a locker as "failed" (typically because the owning thread
222 * died). Releases all its locks and aborts any waits. */
223int xtc_lockmgr_failchk(xtc_lockmgr_t *mgr, xtc_locker_t locker);
224
225int xtc_lockmgr_stat(const xtc_lockmgr_t *mgr, xtc_lockmgr_stat_t *out);
226int xtc_lockmgr_n_held(const xtc_lockmgr_t *mgr);
227int xtc_lockmgr_n_waiting(const xtc_lockmgr_t *mgr);
228
229#endif /* XTC_LOCKMGR_H */