libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_sync.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_sync.h
6 * L3 synchronization primitives. M9 ships:
7 *
8 * notify Tokio-style one-shot wake-of-any-waiter
9 * semaphore counting; backpressure currency
10 * abort_source Seastar-style structured cancellation
11 *
12 * The full M9 surface (mutex, rwlock, barrier, gate) lands in
13 * M9.5 alongside the lock-manager work in M13. These three are
14 * the minimum needed for the M10 supervisor.
15 */
16
17#ifndef XTC_SYNC_H
18#define XTC_SYNC_H
19
20#include <stdint.h>
21
22#include "xtc.h"
23#include "xtc_loop.h"
24
25/* ----- notify ----------------------------------------------------- */
26
27typedef struct xtc_notify xtc_notify_t;
28
29/*
30 * PUBLIC: int xtc_notify_create __P((xtc_notify_t **));
31 * PUBLIC: void xtc_notify_destroy __P((xtc_notify_t *));
32 * PUBLIC: int xtc_notify_signal __P((xtc_notify_t *));
33 * PUBLIC: int xtc_notify_wait __P((xtc_notify_t *, int64_t));
34 */
35int xtc_notify_create(xtc_notify_t **out);
36void xtc_notify_destroy(xtc_notify_t *n);
37
38/* Wake one waiter. If no one is waiting, the signal is "stored"
39 * and the next wait returns immediately. Subsequent signals
40 * before a wait collapse into one. */
41int xtc_notify_signal(xtc_notify_t *n);
42
43/* Block (yield) the calling task until a signal arrives. timeout_ns
44 * < 0 = forever; 0 = non-blocking; > 0 = bounded. Returns XTC_E_AGAIN
45 * on timeout, XTC_OK on signal received. */
46int xtc_notify_wait(xtc_notify_t *n, int64_t timeout_ns);
47
48/* ----- semaphore -------------------------------------------------- */
49
50typedef struct xtc_sem xtc_sem_t;
51
52/*
53 * PUBLIC: int xtc_sem_create __P((unsigned, xtc_sem_t **));
54 * PUBLIC: void xtc_sem_destroy __P((xtc_sem_t *));
55 * PUBLIC: int xtc_sem_post __P((xtc_sem_t *, unsigned));
56 * PUBLIC: int xtc_sem_acquire __P((xtc_sem_t *, unsigned, int64_t));
57 * PUBLIC: int xtc_sem_try_acquire __P((xtc_sem_t *, unsigned));
58 * PUBLIC: int xtc_sem_count __P((const xtc_sem_t *));
59 */
60int xtc_sem_create(unsigned initial, xtc_sem_t **out);
61void xtc_sem_destroy(xtc_sem_t *s);
62
63/* Add `n` units. */
64int xtc_sem_post(xtc_sem_t *s, unsigned n);
65
66/* Take `n` units, blocking up to timeout_ns. */
67int xtc_sem_acquire(xtc_sem_t *s, unsigned n, int64_t timeout_ns);
68
69/* Take `n` units, returning XTC_E_AGAIN immediately if not enough. */
70int xtc_sem_try_acquire(xtc_sem_t *s, unsigned n);
71
72int xtc_sem_count(const xtc_sem_t *s);
73
74/* ----- abort_source ----------------------------------------------- */
75
76typedef struct xtc_abort_source xtc_abort_source_t;
77typedef struct xtc_abort_token xtc_abort_token_t;
78/*
79 * Structured cancellation. An abort_source is owned by some parent
80 * (e.g. a supervisor) and produces tokens that children check.
81 * When the source is fired, every token answers true to is_aborted.
82 *
83 * PUBLIC: int xtc_abort_source_create __P((xtc_abort_source_t **));
84 * PUBLIC: void xtc_abort_source_destroy __P((xtc_abort_source_t *));
85 * PUBLIC: int xtc_abort_source_fire __P((xtc_abort_source_t *, int));
86 * PUBLIC: int xtc_abort_source_token __P((xtc_abort_source_t *, xtc_abort_token_t *));
87 *
88 * PUBLIC: int xtc_abort_token_is_aborted __P((const xtc_abort_token_t *));
89 * PUBLIC: int xtc_abort_token_reason __P((const xtc_abort_token_t *));
90 */
91int xtc_abort_source_create(xtc_abort_source_t **out);
92void xtc_abort_source_destroy(xtc_abort_source_t *s);
93
94/* Atomically fire the source with a reason code. All current and
95 * future tokens see is_aborted=true. */
96int xtc_abort_source_fire(xtc_abort_source_t *s, int reason);
97
98/* Mint a token bound to the source. */
99int xtc_abort_source_token(xtc_abort_source_t *s, xtc_abort_token_t *out);
100
101/*
102 * Public token shape (so callers can keep one on the stack). The
103 * implementation only reads fields documented here.
104 */
106 xtc_abort_source_t *src;
107};
108
109int xtc_abort_token_is_aborted(const xtc_abort_token_t *t);
110int xtc_abort_token_reason(const xtc_abort_token_t *t);
111
112/* ----- mutex ------------------------------------------------------ */
113
114typedef struct xtc_amutex xtc_amutex_t;
115
116/*
117 * Async parking mutex. When free, the lock is a fast uncontended
118 * flag. When contended, a caller running inside a process /
119 * coroutine parks the fiber (yields to its loop) rather than blocking
120 * the OS thread, so a process can hold the lock across its own park
121 * (e.g. a blocking-pool offload) without wedging the loop when
122 * another process on that loop contends. Fiber waiters form a FIFO
123 * queue with direct hand-off (fair, no thundering herd); a caller
124 * that is not on a loop blocks on a condvar as a fallback.
125 *
126 * PUBLIC: int xtc_amutex_create __P((xtc_amutex_t **));
127 * PUBLIC: int xtc_amutex_create_ex __P((xtc_amutex_t **, unsigned));
128 * PUBLIC: xtc_amutex_t *xtc_amutex_static __P((unsigned));
129 * PUBLIC: void xtc_amutex_destroy __P((xtc_amutex_t *));
130 * PUBLIC: int xtc_amutex_lock __P((xtc_amutex_t *, int64_t));
131 * PUBLIC: int xtc_amutex_try_lock __P((xtc_amutex_t *));
132 * PUBLIC: int xtc_amutex_unlock __P((xtc_amutex_t *));
133 */
134int xtc_amutex_create(xtc_amutex_t **out);
135
136/*
137 * Recursive variant: with XTC_AMUTEX_RECURSIVE the same owner may
138 * re-lock without deadlocking; the lock is released only when the
139 * matching number of unlocks have run. Ownership is tracked by FIBER
140 * identity on a loop (so two fibers sharing one OS thread are distinct
141 * owners) and by OS thread off a loop. Owner/count are maintained
142 * under the mutex's own lock, so the recursion accounting is race-free
143 * across loops.
144 */
145#define XTC_AMUTEX_RECURSIVE 0x1u
146int xtc_amutex_create_ex(xtc_amutex_t **out, unsigned flags);
147
148/*
149 * Process-global static mutexes. Returns a stable, lazily-created
150 * recursive amutex for `slot` (0 .. XTC_AMUTEX_STATIC_MAX-1); repeated
151 * calls with the same slot return the same object. Never destroyed by
152 * the caller. Intended for adapters (e.g. a SQLite mutex vtable) that
153 * need named, never-freed mutexes.
154 */
155#define XTC_AMUTEX_STATIC_MAX 32u
156xtc_amutex_t *xtc_amutex_static(unsigned slot);
157
158void xtc_amutex_destroy(xtc_amutex_t *m);
159int xtc_amutex_lock(xtc_amutex_t *m, int64_t timeout_ns);
160int xtc_amutex_try_lock(xtc_amutex_t *m);
161int xtc_amutex_unlock(xtc_amutex_t *m);
162
163/* ----- arwlock (parking reader/writer latch) --------------------- */
164
165typedef struct xtc_arwlock xtc_arwlock_t;
166
167/*
168 * Shared/exclusive latch whose contended waiters PARK the fiber
169 * (yield to the loop) rather than blocking the OS thread -- the
170 * reader/writer analogue of xtc_amutex. A holder may park on I/O
171 * while latched, and lock coupling may hold a parent latch across a
172 * child fix, without wedging a cooperative loop. Off a loop, waiters
173 * block on a condvar. FIFO fairness: an acquirer that finds a waiter
174 * queued ahead queues too, so a read stream cannot starve a writer.
175 * timeout_ns: <0 wait forever, 0 try, >0 deadline (XTC_E_AGAIN on
176 * timeout). Release with xtc_arwlock_unlock regardless of mode.
177 *
178 * PUBLIC: int xtc_arwlock_create __P((xtc_arwlock_t **));
179 * PUBLIC: void xtc_arwlock_destroy __P((xtc_arwlock_t *));
180 * PUBLIC: int xtc_arwlock_rdlock __P((xtc_arwlock_t *, int64_t));
181 * PUBLIC: int xtc_arwlock_wrlock __P((xtc_arwlock_t *, int64_t));
182 * PUBLIC: int xtc_arwlock_unlock __P((xtc_arwlock_t *));
183 */
184int xtc_arwlock_create(xtc_arwlock_t **out);
185void xtc_arwlock_destroy(xtc_arwlock_t *r);
186int xtc_arwlock_rdlock(xtc_arwlock_t *r, int64_t timeout_ns);
187int xtc_arwlock_wrlock(xtc_arwlock_t *r, int64_t timeout_ns);
188int xtc_arwlock_unlock(xtc_arwlock_t *r);
189
190/* ----- rwlock ----------------------------------------------------- */
191
192typedef struct xtc_rwlock xtc_rwlock_t;
193
194/*
195 * Reader/writer lock with writer priority (writers don't starve).
196 *
197 * PUBLIC: int xtc_rwlock_create __P((xtc_rwlock_t **));
198 * PUBLIC: void xtc_rwlock_destroy __P((xtc_rwlock_t *));
199 * PUBLIC: int xtc_rwlock_rdlock __P((xtc_rwlock_t *, int64_t));
200 * PUBLIC: int xtc_rwlock_wrlock __P((xtc_rwlock_t *, int64_t));
201 * PUBLIC: int xtc_rwlock_unlock __P((xtc_rwlock_t *));
202 */
203int xtc_rwlock_create(xtc_rwlock_t **out);
204void xtc_rwlock_destroy(xtc_rwlock_t *r);
205int xtc_rwlock_rdlock(xtc_rwlock_t *r, int64_t timeout_ns);
206int xtc_rwlock_wrlock(xtc_rwlock_t *r, int64_t timeout_ns);
207int xtc_rwlock_unlock(xtc_rwlock_t *r);
208
209/* ----- barrier ---------------------------------------------------- */
210
211typedef struct xtc_barrier xtc_barrier_t;
212
213/*
214 * N-task rendezvous. Reusable: after the Nth waiter arrives, all
215 * are released and the barrier resets to wait for another N.
216 *
217 * PUBLIC: int xtc_barrier_create __P((unsigned, xtc_barrier_t **));
218 * PUBLIC: void xtc_barrier_destroy __P((xtc_barrier_t *));
219 * PUBLIC: int xtc_barrier_wait __P((xtc_barrier_t *));
220 */
221int xtc_barrier_create(unsigned n, xtc_barrier_t **out);
222void xtc_barrier_destroy(xtc_barrier_t *b);
223int xtc_barrier_wait(xtc_barrier_t *b);
224
225/* ----- gate ------------------------------------------------------- */
226
227typedef struct xtc_gate xtc_gate_t;
228
229/*
230 * Seastar-style gate: counts outstanding operations so callers can
231 * drain. enter/leave around each protected operation; close stops
232 * accepting new entries; drain blocks until count reaches zero.
233 *
234 * Pattern:
235 * xtc_gate_enter(g);
236 * do_work();
237 * xtc_gate_leave(g);
238 * ...
239 * xtc_gate_close(g);
240 * xtc_gate_drain(g, timeout_ns); // wait for in-flight ops to finish
241 *
242 * PUBLIC: int xtc_gate_create __P((xtc_gate_t **));
243 * PUBLIC: void xtc_gate_destroy __P((xtc_gate_t *));
244 * PUBLIC: int xtc_gate_enter __P((xtc_gate_t *));
245 * PUBLIC: int xtc_gate_leave __P((xtc_gate_t *));
246 * PUBLIC: int xtc_gate_close __P((xtc_gate_t *));
247 * PUBLIC: int xtc_gate_drain __P((xtc_gate_t *, int64_t));
248 * PUBLIC: int xtc_gate_count __P((const xtc_gate_t *));
249 */
250int xtc_gate_create(xtc_gate_t **out);
251void xtc_gate_destroy(xtc_gate_t *g);
252int xtc_gate_enter(xtc_gate_t *g);
253int xtc_gate_leave(xtc_gate_t *g);
254int xtc_gate_close(xtc_gate_t *g);
255int xtc_gate_drain(xtc_gate_t *g, int64_t timeout_ns);
256int xtc_gate_count(const xtc_gate_t *g);
257
258#endif /* XTC_SYNC_H */