test(db2-ephemeral): gates opt into WO_EPHEMERAL=1 where a durable table is declared; residency section 7

- residency-accept.sh section 7, six checks: the refusal names the class
  and all three ways forward (exit 2); WO_EPHEMERAL=1 runs from RAM with
  the boot notice and a write round-trips; WO_EPHEMERAL with WO_DATA
  refuses; WO_EPHEMERAL=2 refuses naming the accepted value; keys-resident
  still refuses under the hatch; a plain class (the corpus `methods`
  fixture) runs with no WO_DATA, rc 0, nothing on stderr
- blast radius measured gate by gate — each run without the export first,
  kept only where the program refused: oop-e2e (fixtures declare tables);
  db-bench.py's ram/msgrate/growth/randread legs (the durable legs drop it,
  so a WO_DATA in the caller's shell now refuses loudly instead of silently
  turning a RAM leg durable); db-actor per run (its restart pair sets
  WO_DATA); chat (porch's store declares RateLimitCounter default-durable —
  a library's table binds the consumer); wmux client legs (same image as
  the server, no WO_DATA; servers and the WO_DATA-carrying r11cli `env -u`)
- byte-exact compares (db-actor single-shard, wmux client) drop the one
  notice line; fibers, subprocess, log-watcher declare no table — untouched
- db-bench.py ceiling note: the checked refusal is databasev2 5's now
- residency 32/0, oop-e2e 131/0 with this tree

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 4553ca15da235c155b7ad31bbb077c3ad8e88fee)
This commit is contained in:
shoney.arickathil 2026-09-15 01:03:49 +02:00
parent d38b4f864b
commit 41f48bba3f
5 changed files with 82 additions and 12 deletions

View file

@ -7,6 +7,10 @@
# drain (close frames, exit 0) — functional legs on BOTH WO_IO backends # drain (close frames, exit 0) — functional legs on BOTH WO_IO backends
# plus an ASan run. # plus an ASan run.
set -uo pipefail set -uo pipefail
# databasev2 2 (6a): chat `use`s porch, whose store middleware declares its
# tables default-durable (`RateLimitCounter`, ...) — a library-owned durable
# table binds every consumer, so this RAM-only gate opts in.
export WO_EPHEMERAL=1
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
WOC="$ROOT/compiler/_build/default/bin/woc" WOC="$ROOT/compiler/_build/default/bin/woc"

View file

@ -25,10 +25,13 @@ else
bad "build" "woc failed"; echo "db-actor-accept: 1 checks, 1 failures"; exit 1 bad "build" "woc failed"; echo "db-actor-accept: 1 checks, 1 failures"; exit 1
fi fi
# databasev2 2 (6a): these RAM-only runs opt in with WO_EPHEMERAL=1 (a durable
# table refuses to start without WO_DATA); the restart pair below sets WO_DATA
# instead, and the two are incompatible, so the hatch is per-run, not exported.
check_set() { # name [env pairs...] check_set() { # name [env pairs...]
local name="$1"; shift local name="$1"; shift
local out local out
out="$(env "$@" timeout 30 "$DIR/target/db-actor" 2>&1)" out="$(env WO_EPHEMERAL=1 "$@" timeout 30 "$DIR/target/db-actor" 2>&1)"
if printf '%s' "$out" | grep -q "writer 1 sees sum" \ if printf '%s' "$out" | grep -q "writer 1 sees sum" \
&& printf '%s' "$out" | grep -q "writer 2 sees sum" \ && printf '%s' "$out" | grep -q "writer 2 sees sum" \
&& printf '%s' "$out" | grep -q "^main sees 2 rows, sum 3$" ; then && printf '%s' "$out" | grep -q "^main sees 2 rows, sum 3$" ; then
@ -47,7 +50,8 @@ check_set "multi uring" WO_IO=uring
check_set "multi epoll" WO_IO=epoll check_set "multi epoll" WO_IO=epoll
# single-shard: byte-exact — the local path is untouched # single-shard: byte-exact — the local path is untouched
sout="$(WO_SHARDS=1 timeout 30 "$DIR/target/db-actor" 2>&1)" # byte-exact on merged stdout+stderr, so the hatch's one boot notice is dropped
sout="$(WO_EPHEMERAL=1 WO_SHARDS=1 timeout 30 "$DIR/target/db-actor" 2>&1 | grep -v '^wovm: WO_EPHEMERAL=1')"
want=$'writer 1 sees sum 1\nwriter 2 sees sum 3\nmain sees 2 rows, sum 3' want=$'writer 1 sees sum 1\nwriter 2 sees sum 3\nmain sees 2 rows, sum 3'
if [[ "$sout" == "$want" ]]; then if [[ "$sout" == "$want" ]]; then
ok "single-shard byte-exact" ok "single-shard byte-exact"

View file

@ -19,9 +19,13 @@ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BIN = os.path.join(ROOT, "docs/examples/db-bench/target/db-bench") BIN = os.path.join(ROOT, "docs/examples/db-bench/target/db-bench")
# databasev2 2 task 7. Its own program, not a mode in db-bench: declaring a # databasev2 2 task 7. Its own program, not a mode in db-bench: declaring a
# `resident: keys` table is a WHOLE-PROGRAM constraint — the runtime refuses to # `resident: keys` table is a WHOLE-PROGRAM constraint — the runtime refuses to
# start without WO_DATA, for every mode in the module. Putting those classes in # start without WO_DATA, for every mode in the module, and WO_EPHEMERAL=1 does
# db-bench's shared types made growth/ceiling/randread, which deliberately run # not rescue it. Putting those classes in db-bench's shared types made the ram,
# WITHOUT WO_DATA, refuse to start. # msgrate, growth and randread legs, which deliberately run WITHOUT WO_DATA
# (ceiling sets one — it measures what survives the kill), refuse to start.
# databasev2 2 task 6a: those RAM legs now opt in with WO_EPHEMERAL=1; a
# WO_DATA exported in the caller's shell no longer silently turns them durable,
# it refuses loudly (WO_EPHEMERAL is incompatible with WO_DATA).
RESID_BIN = os.path.join(ROOT, "docs/examples/residency-bench/target/residency-bench") RESID_BIN = os.path.join(ROOT, "docs/examples/residency-bench/target/residency-bench")
WOC = os.path.join(ROOT, "compiler/_build/default/bin/woc") WOC = os.path.join(ROOT, "compiler/_build/default/bin/woc")
WOVM = os.path.join(ROOT, "runtime/wovm") WOVM = os.path.join(ROOT, "runtime/wovm")
@ -188,12 +192,13 @@ def campaign():
for flavor in ("ram", "durable"): for flavor in ("ram", "durable"):
for shards in (1, ncores): for shards in (1, ncores):
tag = f"{flavor}.s{'1' if shards == 1 else 'N'}" tag = f"{flavor}.s{'1' if shards == 1 else 'N'}"
env = {"WO_SHARDS": str(shards)} env = {"WO_SHARDS": str(shards), "WO_EPHEMERAL": "1"}
data = None data = None
if flavor == "durable": if flavor == "durable":
data = os.path.join(ROOT, "bench", f"tmp.{os.getpid()}.{tag}") data = os.path.join(ROOT, "bench", f"tmp.{os.getpid()}.{tag}")
os.makedirs(data, exist_ok=True) os.makedirs(data, exist_ok=True)
env["WO_DATA"] = data env["WO_DATA"] = data
del env["WO_EPHEMERAL"]
rc, lines, rssg, fdg = run(["all", str(N)], env, 1800, sample_after="write ") rc, lines, rssg, fdg = run(["all", str(N)], env, 1800, sample_after="write ")
if rc != 0: if rc != 0:
bad(f"{tag}.all", f"rc={rc} tail={lines[-2:]}") bad(f"{tag}.all", f"rc={rc} tail={lines[-2:]}")
@ -221,7 +226,8 @@ def campaign():
# raises the cap to the flood size — the measured number keeps # raises the cap to the flood size — the measured number keeps
# iteration 22's semantics exactly. # iteration 22's semantics exactly.
rc, lines, _, _ = run(["msgrate", str(MSG_N)], rc, lines, _, _ = run(["msgrate", str(MSG_N)],
{"WO_SHARDS": str(shards), "WO_MAILBOX": str(MSG_N)}, 300) {"WO_SHARDS": str(shards), "WO_MAILBOX": str(MSG_N),
"WO_EPHEMERAL": "1"}, 300)
if rc != 0: if rc != 0:
bad(tag, f"rc={rc}") bad(tag, f"rc={rc}")
else: else:
@ -505,7 +511,7 @@ def growth(metrics):
for shape in GROWTH_SHAPES: for shape in GROWTH_SHAPES:
for legname, swap_mb in (("noswap", 0), ("swap", 256)): for legname, swap_mb in (("noswap", 0), ("swap", 256)):
w = cap_wrapper(512, swap_mb) w = cap_wrapper(512, swap_mb)
env = dict(os.environ) env = dict(os.environ); env["WO_EPHEMERAL"] = "1"
argv = w + [BIN, "growth", str(GROWTH_N), shape] argv = w + [BIN, "growth", str(GROWTH_N), shape]
pr = subprocess.run(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, pr = subprocess.run(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, env=env, timeout=900) text=True, env=env, timeout=900)
@ -554,9 +560,9 @@ def ceiling(metrics):
malloc succeeds and the process dies TOUCHING the pages, so it never malloc succeeds and the process dies TOUCHING the pages, so it never
gets the chance to report failure. (The VM object arena is the gets the chance to report failure. (The VM object arena is the
opposite: WO_HEAP_MB is checked and traps.) rc is asserted, not opposite: WO_HEAP_MB is checked and traps.) rc is asserted, not
recorded as a metric -- when databasev2 2's byte budget lands this recorded as a metric -- when databasev2 5's byte budget (bounded
should become a checked refusal, and the gate must not fail on that tables) lands this should become a checked refusal, and the gate must
improvement. not fail on that improvement.
2. WHAT SURVIVES. With WO_DATA set, replay must yield a contiguous 2. WHAT SURVIVES. With WO_DATA set, replay must yield a contiguous
intact prefix: rows 1..M present with the right v, no holes, and not intact prefix: rows 1..M present with the right v, no holes, and not
@ -772,7 +778,8 @@ def randread(metrics):
w = cap_wrapper(cap_mb, swap_mb) w = cap_wrapper(cap_mb, swap_mb)
pr = subprocess.run(w + [BIN, "randread", str(RAND_N), str(RAND_R)], pr = subprocess.run(w + [BIN, "randread", str(RAND_N), str(RAND_R)],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, env=dict(os.environ), timeout=900) text=True, env=dict(os.environ, WO_EPHEMERAL="1"),
timeout=900)
lines = pr.stdout.splitlines() lines = pr.stdout.splitlines()
ops, p50, p99, hits, filled = parse_randread(lines) ops, p50, p99, hits, filled = parse_randread(lines)
key = f"randread.{legname}" key = f"randread.{legname}"

View file

@ -31,6 +31,9 @@ set -uo pipefail
# narrows determinism to output SETS there); the multi-shard/TSan proofs # narrows determinism to output SETS there); the multi-shard/TSan proofs
# live in the fibers gate, not here. # live in the fibers gate, not here.
export WO_SHARDS=1 # no -e: a failing fixture is handled explicitly, one at a time export WO_SHARDS=1 # no -e: a failing fixture is handled explicitly, one at a time
# databasev2 2 (6a): a program with any durable table (the default) refuses to
# start without WO_DATA; the corpus is RAM-only by contract, so opt in here.
export WO_EPHEMERAL=1
shopt -s nullglob shopt -s nullglob
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

View file

@ -199,6 +199,58 @@ else
bad "resident:keys refusal fixture compiles" "$(head -1 "$WORK/e")" bad "resident:keys refusal fixture compiles" "$(head -1 "$WORK/e")"
fi fi
# ---- 7. a durable table (the default) refuses to run without WO_DATA -------
# databasev2 2 task 6a: before this, a durable class with no WO_DATA ran
# RAM-only and every write was silently discarded — the exact outcome
# `durable: true` promises against. WO_EPHEMERAL=1 is the explicit opt-in to
# that RAM-only run; anything else refuses at startup, exit 2, naming the
# class and all three ways forward.
printf '@table(name: "notes", index: [k])\nclass Notes { k: Text }\nfn main() -> Int { insert Notes { k: "a" }; let n = 0; for x in from r in Notes select r { n = n + 1; } print("notes=${n}"); return 0; }\n' > "$WORK/dur.wo"
if "$WOC" --emit "$WORK/dur.wo" -o "$WORK/dur.wob" 2>"$WORK/e"; then
# (i) default-durable, no WO_DATA, no WO_EPHEMERAL -> refuse
out="$(env -u WO_DATA -u WO_EPHEMERAL "$WOVM" "$WORK/dur.wob" 2>&1)"; rc=$?
[[ $rc -eq 2 ]] && grep -q 'Notes' <<<"$out" && grep -q 'WO_DATA=' <<<"$out" \
&& grep -q 'WO_EPHEMERAL=1' <<<"$out" && grep -q 'durable: false' <<<"$out" \
&& ok "durable table without WO_DATA exits 2, names the class and all three ways forward" \
|| bad "durable table without WO_DATA refuses" "exit=$rc got: $out"
# (ii) WO_EPHEMERAL=1 -> runs from RAM: boot notice on stderr, a write round-trips
out="$(env -u WO_DATA WO_EPHEMERAL=1 "$WOVM" "$WORK/dur.wob" 2>&1)"; rc=$?
[[ $rc -eq 0 ]] && grep -q 'WO_EPHEMERAL=1' <<<"$out" && grep -q 'notes=1' <<<"$out" \
&& ok "WO_EPHEMERAL=1 runs the durable table from RAM (boot notice, write round-trips)" \
|| bad "WO_EPHEMERAL=1 runs from RAM" "exit=$rc got: $out"
# (iii) WO_EPHEMERAL together with WO_DATA -> conflict, refuse
mkdir -p "$WORK/d7"
out="$(WO_DATA="$WORK/d7" WO_EPHEMERAL=1 "$WOVM" "$WORK/dur.wob" 2>&1)"; rc=$?
[[ $rc -eq 2 ]] && grep -q 'incompatible with WO_DATA' <<<"$out" \
&& ok "WO_EPHEMERAL=1 with WO_DATA set exits 2 and names the conflict" \
|| bad "WO_EPHEMERAL + WO_DATA conflict" "exit=$rc got: $out"
# (v) the only accepted value is 1
out="$(env -u WO_DATA WO_EPHEMERAL=2 "$WOVM" "$WORK/dur.wob" 2>&1)"; rc=$?
[[ $rc -eq 2 ]] && grep -q 'WO_EPHEMERAL=1' <<<"$out" \
&& ok "WO_EPHEMERAL=2 exits 2 and names the accepted value" \
|| bad "WO_EPHEMERAL=2 refused" "exit=$rc got: $out"
else
bad "durable refusal fixture compiles" "$(head -1 "$WORK/e")"
fi
# (iv) resident:keys still refuses under WO_EPHEMERAL=1 — the keys loop wins
if [[ -f "$WORK/reskeys.wob" ]]; then
out="$(env -u WO_DATA WO_EPHEMERAL=1 "$WOVM" "$WORK/reskeys.wob" 2>&1)"; rc=$?
[[ $rc -eq 2 ]] && grep -q 'resident: keys' <<<"$out" \
&& ok "WO_EPHEMERAL=1 does not rescue resident:keys (exit 2, keys message)" \
|| bad "WO_EPHEMERAL=1 vs resident:keys" "exit=$rc got: $out"
fi
# (vi) a class that is NOT a @table is not a durable table: no WO_DATA, no
# WO_EPHEMERAL, rc 0 and nothing on stderr. `durable: true` is a @table
# property; the .wob carries the table bit (v8) so the refusal loops can tell.
if "$WOC" --emit tests/corpus/run/methods/fixture.wo -o "$WORK/plain.wob" 2>"$WORK/e"; then
out="$(env -u WO_DATA -u WO_EPHEMERAL "$WOVM" "$WORK/plain.wob" 2>"$WORK/plain.err")"; rc=$?
[[ $rc -eq 0 && "$out" == $'15\n42' ]] && ! grep -q '^wovm:' "$WORK/plain.err" \
&& ok "a plain class (no @table) runs without WO_DATA: rc 0, no wovm: line" \
|| bad "plain class without WO_DATA" "exit=$rc out: $out err: $(cat "$WORK/plain.err")"
else
bad "plain-class fixture compiles" "$(head -1 "$WORK/e")"
fi
# ---- 8. WO_DATA=<file>: the store as one file ------------------------------ # ---- 8. WO_DATA=<file>: the store as one file ------------------------------
# databasev2 7: WO_DATA names either a directory (-> <dir>/shard-0.wal, as it # databasev2 7: WO_DATA names either a directory (-> <dir>/shard-0.wal, as it
# always did) or THE log file. Underneath it is the same wo_wal_* path, so # always did) or THE log file. Underneath it is the same wo_wal_* path, so