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:
parent
897c8442f0
commit
7a614420f4
6 changed files with 184 additions and 0 deletions
3
docs/examples/fibers/.gitignore
vendored
Normal file
3
docs/examples/fibers/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
# built artifacts
|
||||||
|
target/
|
||||||
21
docs/examples/fibers/README.md
Normal file
21
docs/examples/fibers/README.md
Normal 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).
|
||||||
74
docs/examples/fibers/main.wo
Normal file
74
docs/examples/fibers/main.wo
Normal 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;
|
||||||
|
}
|
||||||
6
docs/examples/fibers/wo.toml
Normal file
6
docs/examples/fibers/wo.toml
Normal 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"
|
||||||
6
justfile
6
justfile
|
|
@ -43,6 +43,12 @@ deps-accept:
|
||||||
web-app:
|
web-app:
|
||||||
./scripts/web-app-accept.sh
|
./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
|
# 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
|
# `woc version` + a from-scratch project build+run (self-located wovm) + the
|
||||||
# wo-constraint refusal all work — the "tarball install actually works" gate.
|
# wo-constraint refusal all work — the "tarball install actually works" gate.
|
||||||
|
|
|
||||||
74
scripts/fibers-accept.sh
Executable file
74
scripts/fibers-accept.sh
Executable 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 ]]
|
||||||
Loading…
Reference in a new issue