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