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