🟥 Stackful fiber I/O bridge over Async
SaneCppFibersAsync.h is a bridge between Fibers and Async. It lets fiber tasks call async I/O helpers in synchronous-looking code while preserving the caller-owned request, buffer, and event-loop model from Async.
FibersAsync is the I/O bridge for stackful fibers. It does not replace AsyncEventLoop and it does not replace FiberScheduler. Instead, FiberAsyncIO wraps both:
Fiber tasks can then wait for timers, sockets, files, processes, or signals by calling io.sleep(), io.receive(), io.fileRead(), and similar methods. Internally, the lower-level AsyncRequest starts on the wrapped event loop, and the current fiber suspends until the async completion wakes it.
The library keeps the spirit of Async:
FiberAsyncIO uses an externally owned AsyncEventLoop&;FiberAsyncIO uses an externally owned FiberScheduler&;Result;Async code and fiber-style FibersAsync code can share the same event loop;FiberAsyncCommand storage.FibersAsync is deliberately concrete for now. A more abstract FiberIO facade should wait until there is a second real backend worth sharing behind one API.
The simplest example is a fiber that waits without blocking the OS thread:
io.runUntilComplete() drives both sides: ready fibers through FiberScheduler, and async completions through AsyncEventLoop.
Socket helpers follow the same Sane C++ result-object pattern as Async: operation status is returned as Result, and data about the operation is written into explicit output objects.
receive() writes the actual received byte range into received.data, which points into the caller-provided buffer. sendAll() repeats lower-level send operations until the whole span is sent or an error/cancellation occurs.
File helpers also use caller-provided buffers and optional result objects:
Offsets are explicit through fileReadAt(), fileReadExactAt(), fileWriteAt(), and fileWriteAllAt(). File readiness is available through filePoll() for the currently supported platform/backend cases.
AsyncEventLoop remains owner-thread-affine. If a fiber running on a worker thread starts I/O, FiberAsyncIO posts the start/stop command back to the owner thread through bounded FiberAsyncCommand storage.
The command storage size is a real capacity limit. If cross-thread producers can submit more simultaneous starts/stops than the storage can hold, provide more FiberAsyncCommand slots or design the producer to apply backpressure.
Cancellation is cooperative and result-based. A task suspended in a FiberAsyncIO operation can be canceled through the fiber scheduler; FiberAsyncIO stops the underlying async request when needed and wakes the fiber with an error Result.
FiberAsyncIO::cancelAll() is also available as an I/O bridge helper for canceling pending async operations associated with the bridge.
The lifetime rules are intentionally close to Async and Fibers:
AsyncEventLoop must outlive FiberAsyncIO;FiberScheduler must outlive FiberAsyncIO;FiberAsyncCommand storage must outlive cross-thread operations using it;FiberTask and FiberStack storage must outlive the spawned task;This is the key difference from a heap-backed async framework: the caller chooses the maximum number of simultaneous tasks, stacks, command posts, and buffers.
FibersAsync does not allocate coroutine frames and does not need C++20 coroutines. It relies on the active fiber stack to hold the operation state while the fiber is suspended. Cross-thread command posting uses caller-provided Span<FiberAsyncCommand> storage, and normal same-thread usage can omit command storage.
The lower-level Async and Fibers rules still apply: request objects and fiber objects must be memory-stable, and any capacity that can grow must be supplied explicitly by the caller.
Await and FibersAsync both make Async code easier to read, but they make different tradeoffs.
Await uses C++20 coroutines and explicit AwaitAllocator coroutine-frame allocation. It is a good fit when you want co_await syntax and compiler-generated coroutine state machines.
FibersAsync uses stackful fibers. It is a good fit when you want ordinary synchronous-looking function calls, the ability to suspend through existing nested call stacks, and explicit stack storage instead of coroutine frames.
Both libraries keep callback-style Async integration possible because both wrap an existing AsyncEventLoop& instead of owning a separate I/O runtime.
| FibersAsync API | Description |
|---|---|
| FiberAsyncIO | Synchronous-looking fiber I/O wrapper around an externally owned AsyncEventLoop. |
| FiberAsyncCommand | Bounded command slot for owner-thread I/O posting. |
| FiberAsyncSocketSendResult | Result object populated by send, sendTo, and sendAll. |
| FiberAsyncSocketReceiveResult | Result object populated by receive. |
| FiberAsyncSocketReceiveFromResult | Result object populated by receiveFrom. |
| FiberAsyncFileWriteResult | Result object populated by file write helpers. |
| FiberAsyncFileReadResult | Result object populated by file read helpers. |
| FiberAsyncFileSendOptions | Options for fileSend. |
| FiberAsyncFileSendResult | Result object populated by fileSend. |
| FiberAsyncProcessExitResult | Result object populated by processExit. |
| FiberAsyncSignalResult | Result object populated by signal. |
Examples/FibersDemo shows two sleeping fiber tasks driven by one FiberAsyncIO, then the same bridge used with a worker pool.Tests/Libraries/FibersAsync/FibersAsyncTest.cpp contains focused examples for sleeps, sockets, UDP, files, fileSend, process exit, signals, cancellation, command queue overflow, and worker-pool cross-thread operation posting.Examples/FibersBenchmark focuses on CPU scheduling rather than I/O, but it is useful context for how the same scheduler scales task execution.🟥 Draft
Current support includes:
sleep();accept() and connect();receive();send();sendAll();sendTo() and receiveFrom();fileRead(), fileReadAt(), fileReadExact(), fileReadExactAt();fileWrite(), fileWriteAt(), fileWriteAll(), and fileWriteAllAt();filePoll() where supported by the underlying Async backend;fileSend();processExit();signal();isOwnerThread();FiberScheduler::requestCancel* and FiberAsyncIO::cancelAll();FibersAsyncTest.FiberAsyncIO bridge remains stable.AwaitTask and fiber tasks after both libraries stabilize.FiberIO facade only after at least two real backends exist.