- spawn placement: round-robin across shards (same-shard when the engine is absent/single); the actor's mailbox and delivery belong to its HOME thread — a spawn to another shard travels as an ADOPT envelope, a send as a SEND envelope (mutex-guarded inbox + eventfd wake; the spec's lock-free rings stay a disclosed deviation until 9e measures the mutex) - workers: first envelope triggers lazy full-vm init UNDER the inbox mutex (TSan caught the memset racing a concurrent push, twice — the second was inbox_push reading wake_efd outside the lock; both fixed, gate x8 + battery clean); serve loop = adopt -> run to drained -> wait on the plane (the wake eventfd is watched by io_uring POLL_ADD oneshot / epoll level-triggered on BOTH backends) - ownership across heaps: every allocation stamps rt->shard_id into the header (the field reserved since iteration 2); a drop on the wrong shard routes home as a FREE envelope — the owner's arena stays single-threaded by construction; at teardown routed frees become no-ops (arenas die wholesale) which is what un-danced the freed-mutex ASan SEGV the first ordering had - WO-E222: an actor's state or message type that is (or transitively contains) an inferred-traced class refuses at the spawn/send — with round-robin every actor is potentially remote; corpus-pinned (compile-fail/traced-send, inference-aware: Box contains ?Node) - determinism narrowed per spec: oop-e2e pins WO_SHARDS=1 (exact outputs); the fibers gate grows multi-shard SET assertions + a TSan run (wovm-tsan target; setarch -R fallback for kernel 6.5+ ASLR) - NEXT_RUNNABLE honors engine shutdown for parked workers (deadlock hole closed); io_wait's adopt-wake (rc 1) no longer reads as fatal - battery: oop-e2e 93/0, fibers 10/0 x8 (+WO_IO=epoll), log-watcher 7/0, employee 8/0, web-app 21/0, deps 8/0, runtime tests 16/16 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
108 lines
4.2 KiB
Bash
Executable file
108 lines
4.2 KiB
Bash
Executable file
#!/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
|
|
}
|
|
|
|
# EXACT checks pin one shard (deterministic by construction); the
|
|
# multi-shard runs assert output SETS — the arc's honest narrowing
|
|
check_run "auto" WO_SHARDS=1
|
|
check_run "uring" WO_SHARDS=1 WO_IO=uring
|
|
check_run "epoll" WO_SHARDS=1 WO_IO=epoll
|
|
|
|
# multi-shard (default = all cores): cross-shard placement + envelopes.
|
|
# Order across shards is scheduling; the SET of lines is the contract.
|
|
mout="$("$DIR/target/fibers" 2>&1)"
|
|
mc=$(printf '%s
|
|
' "$mout" | grep -c "count +")
|
|
mt=$(printf '%s
|
|
' "$mout" | grep -c "main tick")
|
|
if [[ "$mc" == "3" && "$mt" == "8" ]] && printf '%s' "$mout" | grep -q "count +3 = 6" && printf '%s' "$mout" | grep -q "sleeper: up" && printf '%s' "$mout" | grep -q "part2 done"; then
|
|
ok "multi-shard: cross-shard actor delivered the full set"
|
|
else
|
|
bad "multi-shard" "$(printf '%s' "$mout" | tr '
|
|
' '|')"
|
|
fi
|
|
|
|
# TSan: the cross-shard path race-checked (multi-shard, both parts)
|
|
make -C "$ROOT/runtime" wovm-tsan -s >/dev/null 2>&1
|
|
if "$WOC" build "$DIR" -o "$DIR/target/fibers_tsan" --runtime "$ROOT/runtime/build/wovm_tsan" >/dev/null 2>&1; then
|
|
tout="$("$DIR/target/fibers_tsan" 2>&1)"
|
|
if printf '%s' "$tout" | grep -q "unexpected memory mapping"; then
|
|
# kernel 6.5+ high-entropy ASLR vs TSan: the standard workaround
|
|
tout="$(setarch "$(uname -m)" -R "$DIR/target/fibers_tsan" 2>&1)"
|
|
fi
|
|
if printf '%s' "$tout" | grep -q "part2 done" && ! printf '%s' "$tout" | grep -qi "ThreadSanitizer"; then
|
|
ok "TSan multi-shard run clean"
|
|
else
|
|
bad "tsan" "$(printf '%s' "$tout" | grep -i -m2 "SUMMARY\|WARNING" | tr '
|
|
' '|')"
|
|
fi
|
|
else
|
|
bad "tsan" "build failed"
|
|
fi
|
|
|
|
# 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 ]]
|