(sigil async)
(sigil async) - Cooperative Async Runtime
Provides a cooperative multitasking runtime with lightweight tasks, async I/O, and timer support. Tasks yield control explicitly via blocking operations (sleep, I/O waits, channel operations).
Basic Usage
(import (sigil async))
(with-async
(go (begin
(display "Task A\n")
(sleep 0.1)
(display "Task A done\n")))
(go (begin
(display "Task B\n")
(sleep 0.05)
(display "Task B done\n"))))I/O Integration
(with-async
(go (begin
(await-readable sock)
(display (socket-read sock)))))Exports
schedulerprocedureScheduler record type.
The scheduler manages concurrent tasks with support for I/O and timer-based waiting.
scheduler?procedureTest if a value is a scheduler struct.
scheduler-run-queueprocedureGet the run-queue field of a scheduler struct.
scheduler-blocked-countprocedureGet the blocked-count field of a scheduler struct.
scheduler-task-countprocedureGet the task-count field of a scheduler struct.
scheduler-io-waitersprocedureGet the io-waiters field of a scheduler struct.
scheduler-fd-waitersprocedureGet the fd-waiters field of a scheduler struct.
scheduler-timer-waitersprocedureGet the timer-waiters field of a scheduler struct.
scheduler-external-waitersprocedureGet the external-waiters field of a scheduler struct.
set-scheduler-run-queue!procedureSet the run-queue field of a scheduler struct.
set-scheduler-blocked-count!procedureSet the blocked-count field of a scheduler struct.
set-scheduler-task-count!procedureSet the task-count field of a scheduler struct.
set-scheduler-io-waiters!procedureSet the io-waiters field of a scheduler struct.
set-scheduler-fd-waiters!procedureSet the fd-waiters field of a scheduler struct.
set-scheduler-timer-waiters!procedureSet the timer-waiters field of a scheduler struct.
set-scheduler-external-waiters!procedureSet the external-waiters field of a scheduler struct.
make-schedulerprocedureCreate a new scheduler.
scheduler-spawnprocedureAdd a new task to the scheduler.
scheduler-runprocedureRun all scheduler tasks until completion or deadlock.
*current-scheduler* is restored on EVERY exit — the normal return branches AND the exception path when a task raises uncaught and the error unwinds through scheduler-run. Restoring only on the normal branches (the historical bug) leaked the scheduler during unwinding, which is the precondition that let an async-port write (e.g. a logger in a dynamic-wind cleanup, or an outer exception handler) attempt to abort-to-prompt with no live prompt — the async-port logging-recursion class. See [[topics/sigil-async-port-logging-recursion]].
We use a guard (catch → restore → re-raise), NOT dynamic-wind: a dynamic-wind after-thunk is silently SKIPPED when an exception unwinds through the call-with-prompt that run-task installs around each task body (a VM unwind quirk — guard, being an exception handler, still fires across the prompt). Task yields use abort-to-prompt to that per-task prompt installed BELOW this frame, so they never reach this guard; only a genuinely uncaught task exception does.
yieldprocedureYield control to the scheduler.
Allows other tasks to run before continuing.
await-readableprocedureWait until a socket is readable.
Yields to the scheduler until the socket has data available.
await-writableprocedureWait until a socket is writable.
Yields to the scheduler until the socket can accept data.
await-readable-anyvariableWait until ANY socket in a list is readable, or a timeout passes.
Registers ONE waiter for the whole list and resumes exactly once — when some socket becomes readable or (with timeout-ms:) when the deadline passes. The caller re-checks actual readiness itself (e.g. with a 0ms socket-select); a timeout resume is indistinguishable from a readable resume by design, which suits periodic-sweep uses (connection timeout reaping).
This is the cooperative alternative to looping a short-timeout native socket-select over a socket set: a busy loop like that stays perpetually runnable, and since the scheduler only polls socket io-waiters when the run-queue is empty, it starves every other task's socket I/O. Suspending on the whole set folds these sockets into the scheduler's own select instead.
A socket that is closed LOCALLY while awaited never reports readable (its fd is excluded from the poll — unlike a peer close, which surfaces as readable EOF), so waits over sockets another task may close should always pass timeout-ms: to stay bounded.
(await-readable-any (cons listen-sock client-socks) timeout-ms: 1000)in-async-context?procedureCheck if currently running in an async context.
Returns #t if running inside with-async, #f otherwise. Useful for writing code that behaves differently in sync vs async contexts.
(if (in-async-context?)
(display "running async\n")
(display "running sync\n"))await-readable-fdprocedureWait until a file descriptor is readable.
Yields to the scheduler until the fd has data available. Use for non-blocking reads from process pipes.
await-writable-fdprocedureWait until a file descriptor is writable.
Yields to the scheduler until the fd can accept data. Use for non-blocking writes to process pipes.
await-readable-fdsvariableWait until ANY of the given raw file descriptors is readable, or until an optional timeout elapses. Returns when the first fd is readable or the deadline passes.
The raw-fd sibling of await-readable-any (which takes socket objects). fds is a list of integer file descriptors; timeout-ms (keyword) is a relative millisecond cap, or #f for no timeout. Resumes exactly ONCE, so it is safe to poll a whole set of fds (e.g. a GLib main context's poll fds) in one call without the multiple-wake hazard of registering a separate waiter per fd. Off the scheduler it degrades to a blocking fd-select.
current-schedulervariable(No description)
with-asyncvariable(No description)
%run-asyncvariable(No description)
resume-asyncvariable(No description)
govariable(No description)
%go-spawnvariable(No description)
sleepvariable(No description)
when-asyncvariable(No description)
if-asyncvariable(No description)
await-externalvariable(No description)
async-prompt-tagvariable(No description)
scheduler-enqueue!variable(No description)
scheduler-dec-blocked!variable(No description)