libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_chan.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_chan.h
7 * The L3 channel taxonomy. M7 ships three:
8 *
9 * oneshot: one sender, one receiver, exactly-one message.
10 * mpsc: bounded multi-producer single-consumer queue.
11 * watch: many-to-many "latest value wins" slot (one frame).
12 *
13 * mpmc and broadcast arrive in M7.5. All channels have explicit
14 * bounded capacity; out-of-capacity behaviour is documented per
15 * type and tracked via xtc_res so callers cannot exhaust memory.
16 *
17 * Each channel exposes both a blocking variant (caller parks on
18 * a waker if the queue is full/empty) and a non-blocking variant
19 * (returns XTC_E_AGAIN immediately).
20 */
21
22#ifndef XTC_CHAN_H
23#define XTC_CHAN_H
24
25#include "xtc_export.h"
26
27#include <stddef.h>
28#include <stdint.h>
29
30#include "xtc.h"
31#include "xtc_loop.h"
32#include "xtc_res.h"
33
34/* ----- oneshot ----------------------------------------------------- */
35
36typedef struct xtc_chan_oneshot xtc_chan_oneshot_t;
37
38/*
39 * PUBLIC: int xtc_chan_oneshot_create __P((xtc_res_t *, xtc_chan_oneshot_t **));
40 * PUBLIC: void xtc_chan_oneshot_destroy __P((xtc_chan_oneshot_t *));
41 * PUBLIC: int xtc_chan_oneshot_send __P((xtc_chan_oneshot_t *, void *));
42 * PUBLIC: int xtc_chan_oneshot_try_recv __P((xtc_chan_oneshot_t *, void **));
43 * PUBLIC: int xtc_chan_oneshot_set_waker __P((xtc_chan_oneshot_t *, const xtc_waker_t *));
44 */
45XTC_API int xtc_chan_oneshot_create(xtc_res_t *res, xtc_chan_oneshot_t **out);
46XTC_API void xtc_chan_oneshot_destroy(xtc_chan_oneshot_t *c);
47
48/*
49 * Send the single message. Idempotent on a closed channel: a second
50 * send returns XTC_E_INVAL. Always succeeds in the open case (the
51 * slot has capacity 1). If a waker has been registered, fires it.
52 */
53XTC_API int xtc_chan_oneshot_send(xtc_chan_oneshot_t *c, void *msg);
54
55/* Non-blocking receive. Returns XTC_E_AGAIN if nothing yet. */
56XTC_API int xtc_chan_oneshot_try_recv(xtc_chan_oneshot_t *c, void **out);
57
58/*
59 * Register a waker to fire when send happens. Replaces any prior
60 * waker. The receiver pattern: register waker, return PENDING from
61 * the task; when waker fires, try_recv.
62 */
63XTC_API int xtc_chan_oneshot_set_waker(xtc_chan_oneshot_t *c, const xtc_waker_t *w);
64
65/* ----- mpsc bounded ------------------------------------------------ */
66
67typedef struct xtc_chan_mpsc xtc_chan_mpsc_t;
68
69/*
70 * PUBLIC: int xtc_chan_mpsc_create __P((xtc_res_t *, size_t, xtc_chan_mpsc_t **));
71 * PUBLIC: void xtc_chan_mpsc_destroy __P((xtc_chan_mpsc_t *));
72 * PUBLIC: int xtc_chan_mpsc_try_send __P((xtc_chan_mpsc_t *, void *));
73 * PUBLIC: int xtc_chan_mpsc_try_recv __P((xtc_chan_mpsc_t *, void **));
74 * PUBLIC: int xtc_chan_mpsc_set_waker __P((xtc_chan_mpsc_t *, const xtc_waker_t *));
75 * PUBLIC: int xtc_chan_mpsc_close __P((xtc_chan_mpsc_t *));
76 * PUBLIC: size_t xtc_chan_mpsc_len __P((const xtc_chan_mpsc_t *));
77 */
78XTC_API int xtc_chan_mpsc_create(xtc_res_t *res, size_t capacity,
79 xtc_chan_mpsc_t **out);
80XTC_API void xtc_chan_mpsc_destroy(xtc_chan_mpsc_t *c);
81
82/*
83 * Try to send. Returns:
84 * XTC_OK on success
85 * XTC_E_AGAIN channel full (caller is responsible for backpressure;
86 * register a waker via _set_waker if you want to
87 * wait for capacity, or use the wrapper xtc_chan_send)
88 * XTC_E_INVAL closed channel
89 * XTC_E_RESOURCE global slot cap (xtc_res XTC_RES_CHAN_SLOTS) hit
90 */
91XTC_API int xtc_chan_mpsc_try_send(xtc_chan_mpsc_t *c, void *msg);
92
93/* Non-blocking receive. Returns XTC_E_AGAIN on empty, and also when the
94 * oldest slot is reserved by a sender that has not finished writing it
95 * (len may then be nonzero); it never spins. The sender's write fires
96 * the waker. */
97XTC_API int xtc_chan_mpsc_try_recv(xtc_chan_mpsc_t *c, void **out);
98
99/* Register the consumer's waker. Fired on every successful send. */
100XTC_API int xtc_chan_mpsc_set_waker(xtc_chan_mpsc_t *c, const xtc_waker_t *w);
101
102/*
103 * Close the channel. Subsequent sends fail; pending receives drain
104 * the buffer then return XTC_E_INVAL on the next call.
105 */
106XTC_API int xtc_chan_mpsc_close(xtc_chan_mpsc_t *c);
107
108XTC_API size_t xtc_chan_mpsc_len(const xtc_chan_mpsc_t *c);
109
110/* ----- watch (latest-value-wins) ----------------------------------- */
111
112typedef struct xtc_chan_watch xtc_chan_watch_t;
113
114/*
115 * PUBLIC: int xtc_chan_watch_create __P((xtc_res_t *, xtc_chan_watch_t **));
116 * PUBLIC: void xtc_chan_watch_destroy __P((xtc_chan_watch_t *));
117 * PUBLIC: int xtc_chan_watch_send __P((xtc_chan_watch_t *, void *));
118 * PUBLIC: int xtc_chan_watch_recv __P((xtc_chan_watch_t *, void **));
119 */
120XTC_API int xtc_chan_watch_create(xtc_res_t *res, xtc_chan_watch_t **out);
121XTC_API void xtc_chan_watch_destroy(xtc_chan_watch_t *c);
122XTC_API int xtc_chan_watch_send(xtc_chan_watch_t *c, void *value);
123XTC_API int xtc_chan_watch_recv(xtc_chan_watch_t *c, void **out);
124
125/* ----- mpmc bounded ----------------------------------------------- */
126
127typedef struct xtc_chan_mpmc xtc_chan_mpmc_t;
128
129/*
130 * Bounded multi-producer multi-consumer queue. M7.5 ships a
131 * mutex-protected ring; the lock-free Vyukov variant is a future
132 * optimisation. Out-of-capacity behaviour matches mpsc:
133 * try_send returns XTC_E_AGAIN, callers register a waker if they
134 * want to wait.
135 *
136 * PUBLIC: int xtc_chan_mpmc_create __P((xtc_res_t *, size_t, xtc_chan_mpmc_t **));
137 * PUBLIC: void xtc_chan_mpmc_destroy __P((xtc_chan_mpmc_t *));
138 * PUBLIC: int xtc_chan_mpmc_try_send __P((xtc_chan_mpmc_t *, void *));
139 * PUBLIC: int xtc_chan_mpmc_try_recv __P((xtc_chan_mpmc_t *, void **));
140 * PUBLIC: int xtc_chan_mpmc_close __P((xtc_chan_mpmc_t *));
141 * PUBLIC: size_t xtc_chan_mpmc_len __P((const xtc_chan_mpmc_t *));
142 */
143XTC_API int xtc_chan_mpmc_create(xtc_res_t *res, size_t capacity,
144 xtc_chan_mpmc_t **out);
145XTC_API void xtc_chan_mpmc_destroy(xtc_chan_mpmc_t *c);
146XTC_API int xtc_chan_mpmc_try_send(xtc_chan_mpmc_t *c, void *msg);
147XTC_API int xtc_chan_mpmc_try_recv(xtc_chan_mpmc_t *c, void **out);
148XTC_API int xtc_chan_mpmc_close(xtc_chan_mpmc_t *c);
149XTC_API size_t xtc_chan_mpmc_len(const xtc_chan_mpmc_t *c);
150
151/* ----- broadcast --------------------------------------------------- */
152
153typedef struct xtc_chan_broadcast xtc_chan_broadcast_t;
154typedef struct xtc_chan_broadcast_recv xtc_chan_broadcast_recv_t;
155
156/*
157 * Broadcast channel: every subscribed receiver sees every message
158 * (best-effort). Each receiver carries its own cursor; if a
159 * receiver lags more than `capacity` messages it observes a
160 * "lagged" indicator and skips ahead to the latest available. This
161 * is Tokio's broadcast semantics.
162 *
163 * Senders never block; the ring is lossy on slow consumers, by
164 * design. For lossless multi-receiver use mpmc + a per-consumer
165 * filter, or supervise the slow consumer.
166 *
167 * PUBLIC: int xtc_chan_broadcast_create __P((xtc_res_t *, size_t, xtc_chan_broadcast_t **));
168 * PUBLIC: void xtc_chan_broadcast_destroy __P((xtc_chan_broadcast_t *));
169 * PUBLIC: int xtc_chan_broadcast_send __P((xtc_chan_broadcast_t *, void *));
170 * PUBLIC: int xtc_chan_broadcast_subscribe __P((xtc_chan_broadcast_t *, xtc_chan_broadcast_recv_t **));
171 * PUBLIC: void xtc_chan_broadcast_unsubscribe __P((xtc_chan_broadcast_recv_t *));
172 * PUBLIC: int xtc_chan_broadcast_recv __P((xtc_chan_broadcast_recv_t *, void **, int *));
173 */
174XTC_API int xtc_chan_broadcast_create(xtc_res_t *res, size_t capacity,
175 xtc_chan_broadcast_t **out);
176XTC_API void xtc_chan_broadcast_destroy(xtc_chan_broadcast_t *c);
177XTC_API int xtc_chan_broadcast_send(xtc_chan_broadcast_t *c, void *msg);
178XTC_API int xtc_chan_broadcast_subscribe(xtc_chan_broadcast_t *c,
179 xtc_chan_broadcast_recv_t **out_recv);
180XTC_API void xtc_chan_broadcast_unsubscribe(xtc_chan_broadcast_recv_t *r);
181
182/*
183 * Receive the next message visible to this subscriber. On success
184 * returns XTC_OK and writes *out plus *lagged (count of skipped
185 * messages because we fell behind). XTC_E_AGAIN if the cursor is
186 * caught up; XTC_E_INVAL if r is NULL.
187 */
188XTC_API int xtc_chan_broadcast_recv(xtc_chan_broadcast_recv_t *r,
189 void **out, int *lagged);
190
191/* ----- demand (pull-based / GenStage backpressure) ----------------- */
192
193typedef struct xtc_chan_demand xtc_chan_demand_t;
194
195/*
196 * Demand-driven channel: the GenStage / reactive-streams backpressure
197 * primitive. A consumer explicitly ASKS for N items with
198 * xtc_chan_demand_ask; the producer's xtc_chan_demand_send succeeds
199 * only while there is outstanding demand, so a fast producer cannot
200 * outrun a slow consumer -- backpressure is expressed as demand, not as
201 * a fixed buffer that silently drops or blocks. Delivered items still
202 * buffer (up to `capacity`) so the consumer pulls at its own pace.
203 *
204 * xtc_chan_demand_send returns XTC_E_AGAIN when demand is exhausted (the
205 * producer should stop until the consumer asks again -- register a
206 * waker to be told when demand is granted). The consumer drains with
207 * xtc_chan_demand_recv, which does NOT itself grant demand; the
208 * consumer decides its own high-water policy by calling _ask.
209 *
210 * PUBLIC: int xtc_chan_demand_create __P((xtc_res_t *, size_t, xtc_chan_demand_t **));
211 * PUBLIC: void xtc_chan_demand_destroy __P((xtc_chan_demand_t *));
212 * PUBLIC: int xtc_chan_demand_ask __P((xtc_chan_demand_t *, size_t));
213 * PUBLIC: int xtc_chan_demand_send __P((xtc_chan_demand_t *, void *));
214 * PUBLIC: int xtc_chan_demand_try_recv __P((xtc_chan_demand_t *, void **));
215 * PUBLIC: size_t xtc_chan_demand_outstanding __P((const xtc_chan_demand_t *));
216 * PUBLIC: size_t xtc_chan_demand_len __P((const xtc_chan_demand_t *));
217 * PUBLIC: int xtc_chan_demand_set_producer_waker __P((xtc_chan_demand_t *, const xtc_waker_t *));
218 * PUBLIC: int xtc_chan_demand_set_consumer_waker __P((xtc_chan_demand_t *, const xtc_waker_t *));
219 * PUBLIC: int xtc_chan_demand_close __P((xtc_chan_demand_t *));
220 */
221XTC_API int xtc_chan_demand_create(xtc_res_t *res, size_t capacity,
222 xtc_chan_demand_t **out);
223XTC_API void xtc_chan_demand_destroy(xtc_chan_demand_t *c);
224
225/* Grant `n` units of demand. Wakes the producer's waker if set. */
226XTC_API int xtc_chan_demand_ask(xtc_chan_demand_t *c, size_t n);
227
228/* Send an item, consuming one unit of demand. XTC_E_AGAIN if demand is
229 * exhausted or the buffer is full; XTC_E_INVAL if closed. */
230XTC_API int xtc_chan_demand_send(xtc_chan_demand_t *c, void *msg);
231
232/* Non-blocking receive. XTC_E_AGAIN if empty. Does not grant demand. */
233XTC_API int xtc_chan_demand_try_recv(xtc_chan_demand_t *c, void **out);
234
235/* Outstanding (un-consumed) demand. */
236XTC_API size_t xtc_chan_demand_outstanding(const xtc_chan_demand_t *c);
237
238/* Buffered items not yet received. */
239XTC_API size_t xtc_chan_demand_len(const xtc_chan_demand_t *c);
240
241/* Producer waker: fired when the consumer grants demand (xtc_chan_demand_ask). */
242XTC_API int xtc_chan_demand_set_producer_waker(xtc_chan_demand_t *c,
243 const xtc_waker_t *w);
244
245/* Consumer waker: fired when the producer sends an item. */
246XTC_API int xtc_chan_demand_set_consumer_waker(xtc_chan_demand_t *c,
247 const xtc_waker_t *w);
248
249/* Close the channel; subsequent sends fail, buffered items still drain. */
250XTC_API int xtc_chan_demand_close(xtc_chan_demand_t *c);
251
252#endif /* XTC_CHAN_H */