std1.0.0-beta-7 SafeC docs

Module sched::reactor

#include <std/sched/reactor.h> source

Real-time I/O scheduler (reactor)

A single-thread event loop that lets many concurrent network, file, and signal operations make progress on one real OS thread, without ever blocking the whole program on any single one of them — "async" in the same sense as epoll/kqueue-based runtimes elsewhere, but built directly on SafeC's existing cooperative TaskScheduler (std/sync/task.h) rather than introducing a second, competing concurrency primitive.

This module is a new, hosted-only reactor operating on real OS file descriptors from real OS syscalls (open/socket + the platform's event queue). It's deliberately separate from std/net/tcp.sc, which is a from-scratch, bare-metal-oriented packet-level TCP/IP stack with no OS socket syscalls at all — the two serve different targets (hosted userspace vs. no-OS-underneath embedded) and neither is a drop-in replacement for the other.

This header declares the portable struct Reactor/API; each backend .sc file below implements the same surface against that platform's actual event-notification syscall:

reactor_kqueue.sc  — macOS, iOS, FreeBSD (kqueue/kevent)
reactor_epoll.sc   — Linux, Android (epoll; signals via signalfd)
reactor_win32.sc   — Windows (WSAPoll; SCHED_SIGNAL is a no-op — see
                     that file's header comment for why)

Pick the one matching your target by including it directly (or via prelude.h, which includes none of them — see each file's own comment); safeguard's stdlib build tolerates the other two failing to compile/ assemble for the current target and skips them (see std/sched.md).

Usage shape:

1. Spawn tasks on a std::TaskScheduler as usual (see task.h) — each
   task follows the same 'int(?&Erased arg, int resume_point)' yield
   protocol.
2. A task that would otherwise block on I/O calls
   'sched->await_fd(fd, SCHED_READ)' (or SCHED_WRITE/SCHED_SIGNAL)
   before returning its yield value, instead of actually blocking.
3. Drive the whole thing with std::reactor_run(sched, reactor) instead
   of TaskScheduler::run_all() — it ticks every runnable task, and
   whenever nothing is immediately runnable, blocks in the OS's event
   queue (zero CPU spin) until the next fd becomes ready, then resumes
   exactly the task(s) waiting on it.

Structs

SigFdEntrysignum -> backend-fd mapping used by reactor_epoll.sc's signalfd-based SCHED_SIGNAL support (see struct Reactor::sigfds below). Declared here, not in reactor_epoll.sc, purely so it's a stable shared type regardless of which backend is actually compiled in.
WatchedFd(fd, filter) pair used by reactor_win32.sc's registration table (see struct Reactor::watched below). Declared here for the same reason as SigFdEntry above.
Reactor

Functions

reactor_runDrives 'sched' to completion: repeatedly ticks every runnable task, and whenever none are immediately runnable, polls the reactor — with a zero timeout if some tasks are merely mid-round (so newly-ready I/O is picked up without stalling), or an indefinite wait if literally everything remaining is blocked on I/O, so the process consumes zero CPU until the OS actually has something to report. This is the scheduler itself: replaces TaskScheduler::run_all() for any workload where tasks call await_fd().

Macros

SCHED_READPortable readiness filter ids used by Reactor::add/remove and TaskScheduler::await_fd/unblock_fd — translated internally to OS-specific values (e.g. EVFILT_READ/WRITE/SIGNAL for the kqueue backend, EPOLLIN/EPOLLOUT+signalfd for epoll). For SCHED_SIGNAL, the 'fd' parameter of add/remove/await_fd is actually a signal number, not a file descriptor.
SCHED_WRITE
SCHED_SIGNAL