sigildocs

(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

schedulerprocedure

Scheduler record type.

The scheduler manages concurrent tasks with support for I/O and timer-based waiting.

scheduler?procedure

Test if a value is a scheduler struct.

Get the run-queue field of a scheduler struct.

Get the blocked-count field of a scheduler struct.

Get the task-count field of a scheduler struct.

Get the io-waiters field of a scheduler struct.

Get the fd-waiters field of a scheduler struct.

Get the timer-waiters field of a scheduler struct.

Get the external-waiters field of a scheduler struct.

Set the run-queue field of a scheduler struct.

Set the blocked-count field of a scheduler struct.

Set the task-count field of a scheduler struct.

Set the io-waiters field of a scheduler struct.

Set the fd-waiters field of a scheduler struct.

Set the timer-waiters field of a scheduler struct.

Set the external-waiters field of a scheduler struct.

Create a new scheduler.

Add a new task to the scheduler.

scheduler-runprocedure

Run 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.

yieldprocedure

Yield control to the scheduler.

Allows other tasks to run before continuing.

Wait until a socket is readable.

Yields to the scheduler until the socket has data available.

Wait until a socket is writable.

Yields to the scheduler until the socket can accept data.

Wait 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)

Check 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"))

Wait until a file descriptor is readable.

Yields to the scheduler until the fd has data available. Use for non-blocking reads from process pipes.

Wait until a file descriptor is writable.

Yields to the scheduler until the fd can accept data. Use for non-blocking writes to process pipes.

Wait 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.

(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)

(No description)

(No description)

(No description)

(No description)