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