- root cause: a worker's runtime is initialised lazily on first fiber adoption, and rt.shard_id is stamped only there — but INBOX_READY[i] is set at thread creation. A shard that never adopts is still settled at shutdown, carrying rt.shard_id 0 from the memset - it then impersonated shard 0: wo_drop_obj saw 0 == 0 for anything the primary allocated, took the "we are home" branch instead of routing, and called class_free against rt->classes, which lazy init never filled. &rt->classes[class_id] off a NULL base is the faulting read - fix: stamp the runtime's real identity at thread creation. An uninitialised shard owns nothing, so its true id makes every payload correctly foreign and routes it to an owner that can free it - ASan could not name this: the arena is one hand-managed malloc block, so intra-arena reuse is invisible and it surfaces as a bare SEGV - pinned by tests/regress/lang-41, driven from db-actor-accept. Needs multiple shards (the corpus runner pins WO_SHARDS=1) and the ASan build. SEGVs twice per run unfixed, clean fixed - the HANG is a separate defect and is NOT fixed: with this in place the harness stops losing whole sections, but idempotent-stop-2 still fires ~1 run in 6. The story records where to look Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit 9dca0b4b4727b976d326b29cb4c6522b62d48a73)
106 lines
4.3 KiB
Bash
Executable file
106 lines
4.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# scripts/db-actor-accept.sh — arc stage 3's gate: actors on worker shards
|
|
# read and write the database through the transparent DB actor. Multi-shard
|
|
# output is asserted as a SET (scheduling orders the writer lines); the
|
|
# main line and the single-shard run are exact. The WO_DATA pair proves a
|
|
# worker's write rides the owner's WAL and replays.
|
|
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/db-actor"
|
|
|
|
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 "db-actor-accept: build woc and wovm first" >&2; exit 1
|
|
fi
|
|
|
|
if "$WOC" build "$DIR" -o "$DIR/target/db-actor" --runtime "$WOVM" >/dev/null 2>&1; then
|
|
ok "builds"
|
|
else
|
|
bad "build" "woc failed"; echo "db-actor-accept: 1 checks, 1 failures"; exit 1
|
|
fi
|
|
|
|
check_set() { # name [env pairs...]
|
|
local name="$1"; shift
|
|
local out
|
|
out="$(env "$@" timeout 30 "$DIR/target/db-actor" 2>&1)"
|
|
if printf '%s' "$out" | grep -q "writer 1 sees sum" \
|
|
&& printf '%s' "$out" | grep -q "writer 2 sees sum" \
|
|
&& printf '%s' "$out" | grep -q "^main sees 2 rows, sum 3$" ; then
|
|
ok "$name: both writers wrote and read cross-shard; main exact"
|
|
else
|
|
bad "$name" "$(printf '%s' "$out" | tr '\n' '|')"
|
|
fi
|
|
}
|
|
|
|
# multi-shard (default = all cores): the RPC path under test, three rounds
|
|
check_set "multi #1"
|
|
check_set "multi #2"
|
|
check_set "multi #3"
|
|
# forced backends: the reply park is plane-independent
|
|
check_set "multi uring" WO_IO=uring
|
|
check_set "multi epoll" WO_IO=epoll
|
|
|
|
# single-shard: byte-exact — the local path is untouched
|
|
sout="$(WO_SHARDS=1 timeout 30 "$DIR/target/db-actor" 2>&1)"
|
|
want=$'writer 1 sees sum 1\nwriter 2 sees sum 3\nmain sees 2 rows, sum 3'
|
|
if [[ "$sout" == "$want" ]]; then
|
|
ok "single-shard byte-exact"
|
|
else
|
|
bad "single-shard" "$(printf '%s' "$sout" | tr '\n' '|')"
|
|
fi
|
|
|
|
# durability through the RPC: a worker's insert commits on the owner's WAL
|
|
# before the ack; a restart replays it (2 rows, then 2+2)
|
|
DATA="$(mktemp -d)"
|
|
r1="$(WO_DATA="$DATA" timeout 30 "$DIR/target/db-actor" 2>&1 | tail -1)"
|
|
r2="$(WO_DATA="$DATA" timeout 30 "$DIR/target/db-actor" 2>&1 | tail -1)"
|
|
rm -rf "$DATA"
|
|
if [[ "$r1" == "main sees 2 rows, sum 3" && "$r2" == "main sees 4 rows, sum 6" ]]; then
|
|
ok "WAL: worker writes ack-after-durable, replay doubles the store"
|
|
else
|
|
bad "WAL replay" "r1=$r1 r2=$r2"
|
|
fi
|
|
|
|
# ---- language 41: an unadopted shard must not impersonate shard 0 ---------
|
|
# A worker shard's runtime is initialised lazily, when it adopts its first
|
|
# fiber -- but INBOX_READY is set at thread creation. A shard that never
|
|
# adopts therefore still gets settled at shutdown, and before the fix its
|
|
# rt.shard_id was left 0 by the memset. It then impersonated shard 0:
|
|
# wo_drop_obj saw 0 == 0 for anything the primary allocated, took the "we
|
|
# are home" branch instead of routing, and called class_free against a
|
|
# class table lazy init never filled -- &rt->classes[id] off a NULL base.
|
|
#
|
|
# Needs MULTIPLE SHARDS (the corpus runner pins WO_SHARDS=1, which is why
|
|
# this lives here) and the ASan build, because the arena is one hand-managed
|
|
# malloc block: intra-arena reuse is invisible to ASan, so the failure
|
|
# surfaces as a bare SEGV rather than a use-after-free report.
|
|
L41_W="$(mktemp -d)"
|
|
trap 'rm -rf "$L41_W"' EXIT
|
|
L41_WOC="$ROOT/compiler/_build/default/bin/woc"
|
|
L41_VM="$ROOT/runtime/build/wovm_asan"
|
|
L41_SRC="$ROOT/tests/regress/lang-41/shard-settle-crash.wo"
|
|
if [[ ! -x "$L41_VM" ]]; then
|
|
bad "lang-41 shard settle" "build it first: make -C runtime wovm-asan"
|
|
elif "$L41_WOC" --emit "$L41_SRC" -o "$L41_W/l41.wob" >/dev/null 2>&1; then
|
|
mkdir -p "$L41_W/l41data"
|
|
l41_out="$(WO_DATA="$L41_W/l41data" WO_SHARDS=4 timeout 60 "$L41_VM" "$L41_W/l41.wob" 2>&1)"
|
|
if grep -q 'SEGV\|AddressSanitizer' <<<"$l41_out"; then
|
|
bad "lang-41 shard settle" "$(grep -m1 'ERROR' <<<"$l41_out")"
|
|
elif grep -q 'dispatched' <<<"$l41_out"; then
|
|
ok "lang-41: an unadopted shard routes instead of impersonating shard 0"
|
|
else
|
|
bad "lang-41 shard settle" "no output: $(head -c 120 <<<"$l41_out")"
|
|
fi
|
|
else
|
|
bad "lang-41 shard settle" "fixture did not compile"
|
|
fi
|
|
|
|
echo
|
|
echo "db-actor-accept: $((pass + fail)) checks, $fail failures"
|
|
[[ $fail -eq 0 ]] || exit 1
|