libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_async.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_async.h
7 * The L2 coroutine surface. Stackful fibers via ucontext (M4),
8 * protothreads (always available), and the macros that let user
9 * code write sync-looking async logic.
10 *
11 * See M4_CLAIMS.md.
12 */
13
14#ifndef XTC_ASYNC_H
15#define XTC_ASYNC_H
16
17#include "xtc_export.h"
18
19#include <stddef.h>
20#include <stdint.h>
21
22#include "xtc_loop.h"
23
24/*
25 * Coroutine entry function. The argument is the user's `arg`; the
26 * return value is recovered by the awaiter via xtc_await().
27 *
28 * The signature uses intptr_t so any pointer or fixed-width integer
29 * fits without per-call allocation. Wider returns can be done by
30 * setting an out-parameter inside the user struct passed via arg.
31 */
32typedef intptr_t (*xtc_coro_fn)(void *arg);
33
34/*
35 * PUBLIC: int xtc_async __P((xtc_loop_t *, xtc_coro_fn, void *, xtc_task_t **));
36 * PUBLIC: int xtc_await __P((xtc_task_t *, intptr_t *));
37 * PUBLIC: void xtc_yield __P((void));
38 * PUBLIC: void xtc_yield_set_budget __P((xtc_loop_t *, int64_t));
39 * PUBLIC: int xtc_yield_check __P((void));
40 * PUBLIC: int xtc_yield_if_due __P((void));
41 * PUBLIC: uint64_t xtc_yield_due_count __P((const xtc_loop_t *));
42 * PUBLIC: size_t xtc_stack_size __P((void));
43 * PUBLIC: int xtc_set_stack_size __P((size_t));
44 */
45
46/*
47 * xtc_async --
48 * Spawn fn(arg) as a stackful coroutine. Returns immediately
49 * with the task handle in *out_task. The fiber starts in the
50 * scheduled state and runs on the next loop step.
51 */
52XTC_API int xtc_async(xtc_loop_t *loop, xtc_coro_fn fn, void *arg,
53 xtc_task_t **out_task);
54
55/*
56 * xtc_await --
57 * Wait for `t` to complete and recover its return value into
58 * *result. Must be called from inside a coroutine spawned via
59 * xtc_async (or from the main thread before xtc_loop_run).
60 *
61 * Returns XTC_OK on success.
62 */
63XTC_API int xtc_await(xtc_task_t *t, intptr_t *result);
64
65/*
66 * xtc_yield --
67 * From inside a coroutine, return control to the loop. The next
68 * loop step resumes at the line after the call.
69 */
70XTC_API void xtc_yield(void);
71
72/*
73 * Cooperative yield watchdog. xtc has no forcible preemption, so a
74 * long compute loop must cooperate. Set a per-loop time budget with
75 * xtc_yield_set_budget (ns; 0 disables, the default), then in the
76 * compute loop call xtc_yield_if_due() -- it yields when the current
77 * run quantum has exceeded the budget. xtc_yield_check() is the
78 * queryable form (1 == over budget) for embedders that want to react
79 * differently (e.g. fire an abort token -> xtc_svr_call_abortable).
80 * xtc_yield_due_count() reports how many times a task went over
81 * budget on the loop (telemetry). All are no-ops / 0 off a loop.
82 */
83XTC_API void xtc_yield_set_budget(xtc_loop_t *loop, int64_t budget_ns);
84XTC_API int xtc_yield_check(void);
85XTC_API int xtc_yield_if_due(void);
86XTC_API uint64_t xtc_yield_due_count(const xtc_loop_t *loop);
87
88/*
89 * Default fiber stack size in bytes. Configurable per process via
90 * xtc_set_stack_size(). M4 default: 64 KiB.
91 */
92XTC_API size_t xtc_stack_size(void);
93XTC_API int xtc_set_stack_size(size_t bytes);
94
95/*
96 * Stack-memory reclamation on park (Lever S1, M_PREEMPTION section 8).
97 *
98 * A parked stackful fiber commits only the pages it touched but its
99 * mmap'd stack RESERVES the full configured size. With reclaim ON, a
100 * fiber that parks (xtc_yield / recv / a latch or timer wait) returns
101 * the UNUSED tail of its stack -- the region above its current stack
102 * pointer, beyond a small live margin -- to the OS with
103 * madvise(MADV_DONTNEED); it faults back as zero-fill on resume. This
104 * cuts the per-parked-fiber RAM floor for the many-idle-connection case
105 * (measure with bench/bench_mem_per_task). Predictable: no relocation,
106 * no segmented-stack thrash.
107 *
108 * Portable: a no-op where madvise/MADV_DONTNEED is unavailable
109 * (Windows) and on the Win32-fiber substrate (whose stacks the OS
110 * owns). OFF by default (the cooperative fast path is unchanged); a
111 * process-wide opt-in. keep_bytes is the live margin left mapped below
112 * the saved SP (0 selects a sensible default of one page); the reclaim
113 * only fires when the reclaimable tail exceeds a page, to avoid fault
114 * churn on shallow, frequently-woken fibers.
115 *
116 * PUBLIC: int xtc_stack_reclaim_enable __P((size_t));
117 * PUBLIC: void xtc_stack_reclaim_disable __P((void));
118 * PUBLIC: int xtc_stack_reclaim_enabled __P((void));
119 * PUBLIC: uint64_t xtc_stack_reclaim_count __P((void));
120 */
121XTC_API int xtc_stack_reclaim_enable(size_t keep_bytes);
122XTC_API void xtc_stack_reclaim_disable(void);
123XTC_API int xtc_stack_reclaim_enabled(void);
124XTC_API uint64_t xtc_stack_reclaim_count(void);
125
126/*
127 * XTC_COOP_REGION { ... } --
128 * A block that is guaranteed to run to completion without the
129 * scheduler interleaving another task between its statements.
130 * M4 implementation: the macro is a documentation marker only,
131 * because the M4 scheduler is single-threaded and never
132 * preempts a running coroutine outside an explicit yield point.
133 * M5 (multi-loop) hardens this by setting a per-task "do not
134 * steal" flag for the duration of the block.
135 */
136#define XTC_COOP_REGION /* see xtc_async(3) */
137
138/*
139 * Protothread macros (constrained-platform fallback). Bodies of
140 * these functions cannot use stack-resident locals; lift them into a
141 * state struct or static. Documented in xtc_async(3).
142 */
143typedef struct xtc_pt {
144 unsigned short lc; /* local-continuation cookie */
145} xtc_pt_t;
146
147#define XTC_PT_THREAD(...) char __VA_ARGS__
148#define XTC_PT_INIT(pt) ((pt)->lc = 0)
149#define XTC_PT_BEGIN(pt) switch ((pt)->lc) { case 0:
150#define XTC_PT_END(pt) } (pt)->lc = 0; return 2 /* DONE */
151#define XTC_PT_YIELD(pt) do { \
152 (pt)->lc = __LINE__; return 0 /* PENDING */; \
153 case __LINE__:; } while (0)
154#define XTC_PT_WAIT_UNTIL(pt, c) do { \
155 (pt)->lc = __LINE__; case __LINE__: \
156 if (!(c)) return 0; } while (0)
157
158#define XTC_PT_DONE 2
159#define XTC_PT_YIELDED 0
160
161#endif /* XTC_ASYNC_H */