feat(examples): fibers — the hybrid scheduler demonstrated + gate

- docs/examples/fibers: part 1 is TIMING-FREE and byte-exact — main
  sends three messages then burns reductions; each budget expiry hands
  the Counter actor exactly one delivery (cooperative mechanics,
  preemptive fairness, BEAM's shape); part 2 parks a Sleeper actor
  mid-receive on the I/O plane while main keeps ticking — the wake
  lands between ticks, proving a sleeping fiber blocks nobody
- the missing "sleeper: up" on the first run was main-return-reap
  working as specced (main ended before the deadline); the demo's
  window widened so the wake is observable
- scripts/fibers-accept.sh + `just fibers` (8 checks): build, part-1
  exact + part-2 ordering invariants on auto/uring/epoll backends,
  and an ASan-runtime rebuild+run
- README points at the doctrine writeup (exploration/fibers)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
shoney.arickathil 2026-08-20 10:00:40 +02:00
parent 897c8442f0
commit 7a614420f4
6 changed files with 184 additions and 0 deletions

3
docs/examples/fibers/.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# built artifacts
target/

View file

@ -0,0 +1,21 @@
# fibers — the hybrid scheduler, demonstrated
The smallest program that shows all three legs of the concurrency model
(the doctrine-depth writeup lives in
[`docs/plan/exploration/fibers/00-fibers.md`](../../plan/exploration/fibers/00-fibers.md)):
1. **Reduction-budget eviction** — part 1's output is byte-exact and
timing-free: main sends three messages, then burns reductions; every
time its budget expires the counter actor gets a turn and delivers
exactly one message. Cooperative mechanics, preemptive fairness.
2. **Actors as the spawn surface** — `spawn Counter { ... }` returns an
`actor Tick`; `send` moves the message (using it afterwards is a
compile error); delivery is one message at a time per actor.
3. **Parking, not blocking** — part 2's sleeper actor calls `time.sleep`
mid-receive: the fiber parks on the shard's I/O plane (io_uring
primary, epoll fallback — `WO_IO=uring|epoll` forces either) and
main keeps ticking while it sleeps.
Run: `just fibers` (the acceptance gate builds it, checks part 1
byte-exact, asserts part 2's ordering invariants, and repeats the run on
both I/O backends plus ASan).

View file

@ -0,0 +1,74 @@
use time
-- fibers — the writeonce hybrid demonstrated (BEAM's shape):
-- cooperative MECHANICS (no signals, no interrupts) with
-- reduction-budget EVICTION (loop back-edges pay one reduction; at
-- zero the fiber re-queues whether it likes it or not), actors as the
-- spawn surface (one message at a time, ownership-moving sends), and
-- blocking builtins that PARK on the shard's io_uring plane instead
-- of holding the thread.
--
-- Part 1 is deterministic by construction (budget accounting, no time):
-- its output is byte-exact. Part 2 shows a sleeping fiber not blocking
-- anyone; its ordering is asserted loosely by the acceptance gate
-- because it depends on real time.
class Tick {
n: Int
}
-- Part 1: a counting actor; main and the actor interleave one message
-- per main-yield under the default budget.
class Counter {
label: Text
total: Int
fn receive(msg: Tick) {
self.total = self.total + msg.n;
print("${self.label} +${msg.n} = ${self.total}");
}
}
-- Part 2: an actor whose receive PARKS mid-message. The park releases
-- the shard: main keeps ticking while this fiber sleeps.
class Sleeper {
pad: Int
fn receive(msg: Tick) {
print("sleeper: down for ${msg.n}ms");
time.sleep(msg.n);
print("sleeper: up");
}
}
fn spin(rounds: Int) {
-- burn reductions so the scheduler's eviction gets a chance: each
-- back-edge pays one reduction; the default budget is 4000
let i = 0;
while i < rounds {
i = i + 1;
}
}
fn main() -> Int {
-- ---- part 1: deterministic budget interleave ----
let c: actor Tick = spawn Counter { label: "count", total: 0 };
send(c, Tick { n: 1 });
send(c, Tick { n: 2 });
send(c, Tick { n: 3 });
-- three yields deliver exactly three messages, one per turn
spin(5000);
spin(5000);
spin(5000);
print("part1 done");
-- ---- part 2: a parked fiber blocks nobody ----
let s: actor Tick = spawn Sleeper { pad: 0 };
send(s, Tick { n: 150 });
let t = 0;
while t < 8 {
time.sleep(25);
print("main tick ${t}");
t = t + 1;
}
print("part2 done");
return 0;
}

View file

@ -0,0 +1,6 @@
name = "fibers"
version = "0.1.0"
description = "the hybrid scheduler demonstrated: budget eviction + actors + parked sleeps"
[runtime]
wo = ">= 0.1"

View file

@ -43,6 +43,12 @@ deps-accept:
web-app:
./scripts/web-app-accept.sh
# fibers: the hybrid-scheduler demo (docs/examples/fibers) — part 1 byte-
# exact budget interleave, part 2 parked-sleeper-blocks-nobody, on the
# uring AND epoll backends plus an ASan run.
fibers:
./scripts/fibers-accept.sh
# install-accept: extract the dist tarball to a temp prefix, PATH it, and prove
# `woc version` + a from-scratch project build+run (self-located wovm) + the
# wo-constraint refusal all work — the "tarball install actually works" gate.

74
scripts/fibers-accept.sh Executable file
View file

@ -0,0 +1,74 @@
#!/usr/bin/env bash
# scripts/fibers-accept.sh — the hybrid-scheduler demo's gate: part 1 is
# byte-exact (budget accounting, timing-free); part 2 asserts ordering
# invariants (a parked sleeper blocks nobody) on BOTH I/O backends and
# under ASan.
set -uo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WOC="$ROOT/compiler/_build/default/bin/woc"
WOVM="$ROOT/runtime/wovm"
DIR="$ROOT/docs/examples/fibers"
pass=0; fail=0
ok() { echo "ok $1"; pass=$((pass + 1)); }
bad() { echo "FAIL $1 -- $2"; fail=$((fail + 1)); }
if [[ ! -x "$WOC" || ! -x "$WOVM" ]]; then
echo "fibers-accept: build woc and wovm first" >&2; exit 1
fi
if WO_RUNTIME="$WOVM" "$WOC" "$DIR" >/dev/null 2>&1 && [[ -x "$DIR/target/fibers" ]]; then
ok "builds"
else
bad "build" "woc failed"; echo "fibers-accept: 1 checks, 1 failures"; exit 1
fi
check_run() { # name [env pairs...]
local name="$1"; shift
local out
out="$(env "$@" "$DIR/target/fibers" 2>&1)"
local want_p1=$'count +1 = 1\ncount +2 = 3\ncount +3 = 6\npart1 done'
if [[ "$(printf '%s\n' "$out" | head -4)" == "$want_p1" ]]; then
ok "$name: part 1 byte-exact (budget interleave)"
else
bad "$name part1" "$(printf '%s' "$out" | head -4 | tr '\n' '|')"
fi
local down_ln up_ln done_ln ticks
down_ln=$(printf '%s\n' "$out" | grep -n "sleeper: down" | cut -d: -f1)
up_ln=$(printf '%s\n' "$out" | grep -n "sleeper: up" | cut -d: -f1)
done_ln=$(printf '%s\n' "$out" | grep -n "part2 done" | cut -d: -f1)
ticks=$(printf '%s\n' "$out" | grep -c "main tick")
if [[ -n "$down_ln" && -n "$up_ln" && -n "$done_ln" && "$ticks" == "8" ]]; then
local before
before=$(printf '%s\n' "$out" | sed -n "${down_ln},${up_ln}p" | grep -c "main tick")
if [[ "$before" -ge 3 && "$up_ln" -lt "$done_ln" ]]; then
ok "$name: part 2 — parked sleeper blocked nobody ($before ticks while down)"
else
bad "$name part2" "only $before ticks before wake"
fi
else
bad "$name part2" "down=$down_ln up=$up_ln done=$done_ln ticks=$ticks"
fi
}
check_run "auto"
check_run "uring" WO_IO=uring
check_run "epoll" WO_IO=epoll
# ASan flavor: rebuild the binary against the ASan runtime and repeat once
make -C "$ROOT/runtime" wovm-asan -s >/dev/null 2>&1
if "$WOC" build "$DIR" -o "$DIR/target/fibers_asan" --runtime "$ROOT/runtime/build/wovm_asan" >/dev/null 2>&1; then
out="$("$DIR/target/fibers_asan" 2>&1)"
if printf '%s' "$out" | grep -q "part2 done" && ! printf '%s' "$out" | grep -qi "sanitizer\|leak"; then
ok "ASan run clean"
else
bad "asan" "$(printf '%s' "$out" | tail -2 | tr '\n' '|')"
fi
else
bad "asan" "build failed"
fi
echo
printf 'fibers-accept: %d checks, %d failures\n' "$((pass + fail))" "$fail"
[[ $fail -eq 0 ]]