diff --git a/scripts/chat-accept.sh b/scripts/chat-accept.sh index 26dc008..bf5b19b 100755 --- a/scripts/chat-accept.sh +++ b/scripts/chat-accept.sh @@ -7,6 +7,10 @@ # drain (close frames, exit 0) — functional legs on BOTH WO_IO backends # plus an ASan run. 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)" WOC="$ROOT/compiler/_build/default/bin/woc" diff --git a/scripts/db-actor-accept.sh b/scripts/db-actor-accept.sh index 1e91909..932d444 100755 --- a/scripts/db-actor-accept.sh +++ b/scripts/db-actor-accept.sh @@ -25,10 +25,13 @@ else bad "build" "woc failed"; echo "db-actor-accept: 1 checks, 1 failures"; exit 1 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...] local name="$1"; shift 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" \ && printf '%s' "$out" | grep -q "writer 2 sees sum" \ && 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 # 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' if [[ "$sout" == "$want" ]]; then ok "single-shard byte-exact" diff --git a/scripts/db-bench.py b/scripts/db-bench.py index 88441f1..3544338 100755 --- a/scripts/db-bench.py +++ b/scripts/db-bench.py @@ -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") # 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 -# start without WO_DATA, for every mode in the module. Putting those classes in -# db-bench's shared types made growth/ceiling/randread, which deliberately run -# WITHOUT WO_DATA, refuse to start. +# start without WO_DATA, for every mode in the module, and WO_EPHEMERAL=1 does +# not rescue it. Putting those classes in db-bench's shared types made the ram, +# 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") WOC = os.path.join(ROOT, "compiler/_build/default/bin/woc") WOVM = os.path.join(ROOT, "runtime/wovm") @@ -188,12 +192,13 @@ def campaign(): for flavor in ("ram", "durable"): for shards in (1, ncores): 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 if flavor == "durable": data = os.path.join(ROOT, "bench", f"tmp.{os.getpid()}.{tag}") os.makedirs(data, exist_ok=True) env["WO_DATA"] = data + del env["WO_EPHEMERAL"] rc, lines, rssg, fdg = run(["all", str(N)], env, 1800, sample_after="write ") if rc != 0: 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 # iteration 22's semantics exactly. 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: bad(tag, f"rc={rc}") else: @@ -505,7 +511,7 @@ def growth(metrics): for shape in GROWTH_SHAPES: for legname, swap_mb in (("noswap", 0), ("swap", 256)): 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] pr = subprocess.run(argv, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, 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 gets the chance to report failure. (The VM object arena is the opposite: WO_HEAP_MB is checked and traps.) rc is asserted, not - recorded as a metric -- when databasev2 2's byte budget lands this - should become a checked refusal, and the gate must not fail on that - improvement. + recorded as a metric -- when databasev2 5's byte budget (bounded + tables) lands this should become a checked refusal, and the gate must + not fail on that improvement. 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 @@ -772,7 +778,8 @@ def randread(metrics): w = cap_wrapper(cap_mb, swap_mb) pr = subprocess.run(w + [BIN, "randread", str(RAND_N), str(RAND_R)], 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() ops, p50, p99, hits, filled = parse_randread(lines) key = f"randread.{legname}" diff --git a/scripts/oop-e2e.sh b/scripts/oop-e2e.sh index 3ecd8b6..187dc8a 100755 --- a/scripts/oop-e2e.sh +++ b/scripts/oop-e2e.sh @@ -31,6 +31,9 @@ set -uo pipefail # narrows determinism to output SETS there); the multi-shard/TSan proofs # live in the fibers gate, not here. 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 ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" diff --git a/scripts/residency-accept.sh b/scripts/residency-accept.sh index 73a44dc..37a7ba2 100755 --- a/scripts/residency-accept.sh +++ b/scripts/residency-accept.sh @@ -199,6 +199,58 @@ else bad "resident:keys refusal fixture compiles" "$(head -1 "$WORK/e")" 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=: the store as one file ------------------------------ # databasev2 7: WO_DATA names either a directory (-> /shard-0.wal, as it # always did) or THE log file. Underneath it is the same wo_wal_* path, so