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