xtc_async(3)
---xtc_async(3)
L2 stackful coroutines and protothreads
| XTC_ASYNC(3) | Library Functions Manual | XTC_ASYNC(3) |
NAME
xtc_async,
xtc_await, xtc_yield,
xtc_yield_set_budget,
xtc_yield_check,
xtc_yield_if_due,
xtc_yield_due_count,
xtc_stack_size,
xtc_set_stack_size,
XTC_COOP_REGION,
XTC_PT_THREAD, XTC_PT_BEGIN,
XTC_PT_END, XTC_PT_YIELD,
XTC_PT_WAIT_UNTIL — L2
stackful coroutines and protothreads
SYNOPSIS
#include
<xtc_async.h>
int
xtc_async(xtc_loop_t
*loop, xtc_coro_fn
fn, void *arg,
xtc_task_t
**out_task);
int
xtc_await(xtc_task_t
*t, intptr_t
*result);
void
xtc_yield(void);
void
xtc_yield_set_budget(xtc_loop_t
*loop, int64_t
budget_ns);
int
xtc_yield_check(void);
int
xtc_yield_if_due(void);
uint64_t
xtc_yield_due_count(const
xtc_loop_t *loop);
size_t
xtc_stack_size(void);
int
xtc_set_stack_size(size_t
bytes);
Macros:
XTC_COOP_REGION { ... }
XTC_PT_THREAD(name(args)) { XTC_PT_BEGIN(pt); ...; XTC_PT_END(pt); }
XTC_PT_YIELD(pt)
XTC_PT_WAIT_UNTIL(pt, condition)
DESCRIPTION
xtc_async spawns
fn(arg) as a stackful coroutine attached to
loop. The coroutine runs on its own stack (default
6400a0KiB; configurable via
xtc_set_stack_size())
guarded by an unmapped page so stack overflow surfaces as
SIGSEGV rather than silent memory corruption.
A coroutine may freely use stack-resident locals;
they are preserved across
xtc_yield()
and xtc_await(). This is the headline contract that
distinguishes fibers from protothreads.
xtc_await()
blocks the caller until the awaited coroutine has returned, then writes the
coroutine's intptr_t return value to
*result. When called from the main thread, it drives
the loop until the target completes; when called from inside another
coroutine, it parks the caller and resumes it once the target is done.
xtc_yield()
returns control to the loop. The next loop step resumes the coroutine at the
line after the call.
xtc has no forcible preemption, so a
long compute loop must yield to be fair to the rest of the loop.
xtc_yield_set_budget()
arms a per-loop run-quantum time budget in nanoseconds (0, the default,
disables it). With a budget set,
xtc_yield_check()
returns 1 once the current coroutine's quantum has run past the budget
without yielding, and
xtc_yield_if_due()
yields in that case (returning 1 if it did). Sprinkle
xtc_yield_if_due() in a compute loop and a
co-resident coroutine keeps making progress.
xtc_yield_check() is also the queryable "over
budget" signal an embedder can wire to a cancellation token -- fire an
xtc_abort_source when over budget and poll the token
in xtc_svr_call_abortable(3).
xtc_yield_due_count()
reports how many times a coroutine went over budget on the loop (telemetry).
All four are no-ops returning 0 off a loop.
COOPERATIVE REGIONS
The XTC_COOP_REGION block marks a region
that runs to completion without scheduler interleaving. In M4
(single-thread, non-preemptive) it is a documentation marker. In M5
(multi-loop) it sets a “do not steal” flag for the duration of
the block.
PROTOTHREADS
For platforms without a fiber substrate, xtc ships a Duff's-device protothread implementation as a fallback. The price is that protothread bodies cannot use stack-resident locals across yield points: lift them into a state struct or static variables. This restriction is enforced by example, not by the compiler.
A protothread function returns XTC_PT_DONE
when it falls through XTC_PT_END and
XTC_PT_YIELDED when it yields. The caller drives the
protothread by calling it repeatedly until it returns
XTC_PT_DONE.
STACK MANAGEMENT
xtc_stack_size()
returns the default per-coroutine stack size in bytes.
xtc_set_stack_size() overrides it for subsequent
xtc_async()
calls; the value is rounded up to a page multiple, and a sub-1600a0KiB
request returns XTC_E_INVAL.
RETURN VALUES
XTC_OK on success;
XTC_E_INVAL on bad argument;
XTC_E_NOMEM on stack-allocation failure;
XTC_E_INTERNAL on platform failure (getcontext /
makecontext / mmap / mprotect).
THREAD SAFETY
M4 is single-thread. A coroutine spawned on a loop must run only on that loop's thread.
EXAMPLES
static intptr_t multiply(void *arg) {
int v = *(int *)arg;
return (intptr_t)(v * 2);
}
int main(void) {
xtc_loop_t *loop;
xtc_task_t *t;
int v = 21;
intptr_t r;
xtc_loop_init(&loop);
xtc_async(loop, multiply, &v, &t);
xtc_await(t, &r); /* r == 42 */
xtc_loop_fini(loop);
return 0;
}
SEE ALSO
HISTORY
First appeared in xtc 0.0.1 (M4). The ucontext substrate is the M4 default; per-architecture fiber asm (make_fcontext / jump_fcontext) arrives in M4.5 for ~3000a0ns context switches.
| May 25, 2026 | Debian |