Skip to content

test(persistence): G1 cross-plane kill-9 crash matrix — 37 cells, 4 RED root-cause groups (kernel M3 stage 1)#298

Merged
pilotspacex-byte merged 3 commits into
mainfrom
feat/kernel-m3-g1-crash-matrix
Jul 12, 2026
Merged

test(persistence): G1 cross-plane kill-9 crash matrix — 37 cells, 4 RED root-cause groups (kernel M3 stage 1)#298
pilotspacex-byte merged 3 commits into
mainfrom
feat/kernel-m3-g1-crash-matrix

Conversation

@pilotspacex-byte

@pilotspacex-byte pilotspacex-byte commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

New tests/crash_matrix_cross_plane.rs + tests/crash_matrix_cross_plane/ suite: kernel M3 stage 1 (G1) — a RED-first kill-9 crash-recovery tripwire covering every persistence plane moon ships (KV/AOF, KV disk-offload, graph/vector/WS/MQ WAL v3, cross-store MULTI/EXEC, mixed-plane concurrent workloads, and a checkpoint-Finalize-window kill) across {shards=1, shards=4} x {appendonly, disk-offload}. Spec: .planning/reviews/kernel-m3-brief-2026-07-12.md §Stage 1.

This stage does not fix anything found — the RED harness itself is the deliverable. Two coordinator review rounds hardened the suite (root cause vs. harness bug triage, a broken #[ignore]-based RED gate, a vacuous kv-spill precondition, a guard-granularity bug) before this could be trusted as a tripwire.

Cell inventory — 40 cells, 29 GREEN by default, 11 RED (gated)

RED cells are gated by harness::red_guard() (MOON_CRASH_MATRIX_RED=1), not #[ignore = "RED: ..."] — the suite's own --ignored invocation convention runs ignored tests, so an ignore-reason string alone is not a functional gate. MOON_CRASH_MATRIX_ITERS (default 1) enables ad-hoc soaks for probabilistic findings.

4 RED root-cause groups:

  1. Legacy-mode graph reconstruction (6 cells, one cause): cross_plane_legacy_yes_s1_graph_isolated, _txn_isolated_committed, _txn_isolated_atomicity, _mixed_all_planes_synced, _mixed_all_planes_mid_pass_c, plus cross_plane_spot_legacy_s4_graph_isolated. PR fix(shard): gate autovacuum Pass C WAL recycle on checkpoint-backed mode (task #43, P1) #288: graph WAL-v3 replay never reconstructs the graph in legacy (--disk-offload disable) mode — pre-existing, not this stage's job to fix.
  2. MQ resurrection via generic DEL (1 cell): cross_plane_seeded_red_mq_generic_del_resurrection. Same bug class as the already-fixed KV/vector cold-plane resurrection (PR fix(persistence): replayed DEL/FLUSH tombstone the cold plane; spills no longer suppress AOF recovery #257), not yet applied to MQ. The WS half of this brief §1.4 finding turned out GREEN — WorkspaceDrop WAL records replay correctly in order.
  3. Cross-store TXN graph leg not durable — task P2-3: recovery time is O(files × phases) — index manifest entries once #52 (2 cells, NEW): cross_plane_prod_s1_txn_isolated_committed, _s4_txn_isolated_committed. A committed MULTI/EXEC mixing SET + GRAPH.ADDNODE survives kill-9 on the KV leg (PR fix(server): sharded MULTI/EXEC safety, durability & owner-routing (Phase A+B) #247's persist_txn_aof) but loses the graph leg. Deterministic 3x-consecutive repro at both shard counts (--checkpoint-timeout 3600, kill immediately post-sync-wait). Real P1 finding, flagging for its own task.
  4. Checkpoint-Finalize window total-losses the graph plane — task P2-4: add format_version headers to KvLeafPage and DiskANN files #53 (2 cells, NEW): cross_plane_prod_s1_mixed_all_planes_mid_checkpoint, _s4_.... Some kill offsets in the 0-150ms post-BGSAVE window lose the entire already-synced graph batch while KV/vector/WS/MQ all survive. Probabilistic (~1-in-7 to 1-in-12/iteration) — a single default run can false-green even with the guard on; reproduce reliably with MOON_CRASH_MATRIX_RED=1 MOON_CRASH_MATRIX_ITERS=20. Strong P0 candidate for kernel M3 stage 2 (K2), flagging for its own task.

Review round: SHIP-WITH-FIXES → addressed

  • P0 (vacuous spill): kv_spilled_isolated's filler (3000×512B/4MiB) never forced a real cold-tier spill — fixed by mirroring crash_recovery_cold_del_resurrection.rs's proven ratios (16,000×600B/8MiB) plus a hard heap-*.mpf precondition assert. Confirmed genuinely spilling on the VM at both shard counts.
  • P1 (guard granularity): txn_isolated split into txn_isolated_committed (RED, task P2-3: recovery time is O(files × phases) — index manifest entries once #52) and txn_isolated_atomicity (a queued-but-never-committed transaction must apply nothing) — the atomicity claim is unrelated and GREEN on prod_s1/prod_s4; now runs ungated there (still RED on legacy_yes_s1, same root cause as group 1).
  • P2s: wait_for_port_down panics on loop exhaustion instead of silently returning; BenignDisconnectPanicFilter documents its --test-threads=1 requirement; is_unexpected_plane_error switched substring → exact-match; module-doc cell-count self-contradiction fixed; CHANGELOG/doc phrasing corrected (shared helpers added, migration of existing suites' local copies deferred); graph_isolated gained a VALID_AT positive control.

Gates (all green)

  • cargo fmt --check, cargo clippy -- -D warnings (default + tokio/jemalloc matrices, repo-wide and scoped to this test target) — all clean.
  • cargo check --no-default-features --features runtime-tokio,jemalloc --test crash_matrix_cross_plane — compiles both runtimes.
  • VM (orb run -m moon-dev) default green-only run: test result: ok. 40 passed; 0 failed.
  • VM MOON_CRASH_MATRIX_RED=1 run: test result: FAILED. 31 passed; 9 failed (2 probabilistic checkpoint-Finalize cells did not trigger this single-iteration sample, as documented).
  • 3x-consecutive spot-verification on the VM: kv_spilled_isolated passes with a genuine spill at both shard counts; txn_isolated_atomicity GREEN 3x on prod_s1/prod_s4, RED 3x on legacy_yes_s1; txn_isolated_committed RED 3x at both shard counts.

Test-only; no production code changed in this stage.

Test plan

  • cargo fmt --check
  • cargo clippy -- -D warnings (default features)
  • cargo clippy --no-default-features --features runtime-tokio,jemalloc -- -D warnings
  • cargo check --no-default-features --features runtime-tokio,jemalloc --test crash_matrix_cross_plane
  • Full default (green-only) VM run — 40 passed, 0 failed
  • Full MOON_CRASH_MATRIX_RED=1 VM run — 31 passed, 9 failed (matches documented RED inventory)
  • 3x-consecutive re-verification of the two review-round-3 fixes on the VM

Summary by CodeRabbit

  • Tests
    • Added a new cross-plane “kill-9” crash-recovery matrix suite covering persistence, liveness, and data integrity across KV, graph, vector, workspace, and messaging workloads.
    • Introduced concurrent mixed-workload scenarios with synchronized writes and targeted mid-write kill timing.
    • Added regression checks ensuring deleted queues/workspaces do not resurrect after restart, plus seeded RED verifications.
    • Added shared crash-matrix harness utilities (process control, port readiness, server launch, and RESP2 client) and new env-controlled controls for iterations and guarded behavior.
  • Documentation
    • Updated the changelog with matrix scope, default GREEN/RED behavior, known findings, and run guidance.

… / G1)

Adds a RED-first kill-9 crash-recovery tripwire suite covering every
persistence plane moon ships: KV/AOF, KV disk-offload, graph/vector/WS/MQ
WAL v3 effect records, cross-store MULTI/EXEC, mixed-plane concurrent
workloads under load, and a checkpoint-Finalize-window kill. 37 cells run
across {shards=1, shards=4} x {appendonly, disk-offload} — the config the
kernel M3 brief calls out as "the config that matters most" gets full
plane x workload coverage (tests_prod.rs); the rest of the matrix gets
targeted spot/legacy/seeded-RED coverage.

27 cells are GREEN by default; 10 are known-RED and gated behind a runtime
guard (harness::red_guard, MOON_CRASH_MATRIX_RED=1) rather than
#[ignore = "RED: ..."] — the suite's own --ignored invocation convention
(every cell needs a release binary) makes an ignore-reason string alone a
non-functional gate, since --ignored explicitly RUNS ignored tests. A
MOON_CRASH_MATRIX_ITERS env var (default 1) supports ad-hoc soak runs for
probabilistic findings.

Two real findings surfaced during harness development and are deliberately
left RED (fixing them is out of scope for this stage — the RED harness
itself is the deliverable):

- Cross-store TXN graph leg not durable (task #52): a committed MULTI/EXEC
  mixing SET + GRAPH.ADDNODE survives kill-9 on the KV leg but loses the
  graph leg. Deterministic 3x-consecutive repro at both shard counts via
  --checkpoint-timeout 3600 + kill immediately post-sync-wait.
- Checkpoint-Finalize window can total-loss the graph plane (new): some
  kill offsets in the 0-150ms post-BGSAVE window lose the entire synced
  graph batch while KV/vector/WS/MQ all survive. Probabilistic
  (~1-in-7 to 1-in-12/iteration) - needs MOON_CRASH_MATRIX_ITERS=20 to
  reproduce reliably; a single default run can false-green. Strong P0
  candidate for kernel M3 stage 2 (K2).

Also confirmed GREEN (not RED): WS DROP durability under kill-9 -
WorkspaceDrop WAL records replay correctly in order, narrowing the brief's
"MQ/WS resurrection" gap to MQ generic-DEL only (still RED, same bug class
as the already-fixed KV/vector cold-plane resurrection, PR #257).

tests/common/mod.rs: adds find_moon_binary()/sigkill()/wait_for_port_down()
shared helpers the new suite depends on.

Test-only; no production code changed in this stage.

author: Tin Dang
…d granularity (kernel M3 stage 1)

Adversarial review of the G1 cross-plane crash-matrix suite (previous
commit) found two must-fix defects before it could ship as a trustworthy
tripwire, plus several cheap hardening items.

P0 (empirically proven false-GREEN): kv_spilled_isolated's filler
(3000x512B against a 4 MiB --maxmemory cap) never actually forced a
cold-tier spill at either shard count - reproduced on moon-dev with
reclamation_cold_segments:0 and zero heap-*.mpf files. The cell silently
degenerated to ordinary AOF-replay coverage, already proven by
kv_isolated. Fixed by mirroring crash_recovery_cold_del_resurrection.rs's
proven filler ratios (16,000x600B against an 8 MiB cap - shard-count
independent under --maxmemory's per-shard division) plus a hard
count_heap_mpf_files precondition assert, so a future load-parameter
regression fails loud instead of vacuously passing. Confirmed on the VM:
both shard counts now genuinely spill and pass.

P1 (guard granularity): txn_isolated gated both of its rounds behind one
red_guard, but the "transaction queued and killed before EXEC must apply
nothing" atomicity claim has nothing to do with task #52's graph-leg
durability finding and is GREEN on prod_s1/prod_s4 - sharing the guard
hid a working, unrelated regression tripwire by default. Split into
txn_isolated_committed (still RED, task #52) and txn_isolated_atomicity
(ungated on prod_s1/prod_s4; still RED on legacy_yes_s1, folded into the
same pre-existing legacy-graph-reconstruction root cause as every other
legacy graph cell there - confirmed on the VM, not assumed). The initial
split missed that the atomicity round's own GRAPH.CREATE was never
sync-waited before the kill, so its own precondition raced the WAL flush
tick and failed identically on every config regardless of durability
semantics - fixed by sync-waiting GRAPH.CREATE before queuing the
never-committed transaction; the kill point itself is unweakened.

P2s: wait_for_port_down now panics on loop exhaustion instead of
silently returning (no silent-pass verification helpers in a tripwire
codebase); BenignDisconnectPanicFilter documents its --test-threads=1
requirement; is_unexpected_plane_error switched from substring to exact
error-string matching (a corruption message that happened to start with
"unknown index" could otherwise misclassify as benign); module doc's
legacy-family cell count fixed from a self-contradictory "4 cells" /
"5 cells" to a consistent 6 (now includes the new atomicity RED cell);
CHANGELOG/module-doc phrasing corrected to say tests/common's shared
helpers were added but migrating the 5 existing crash_recovery_*.rs
suites' own local copies is deferred, not done; graph_isolated gained a
cheap VALID_AT positive control (a never-invalidated node must still be
visible at a far-future VALID_AT) so the existing negative check cannot
pass vacuously if VALID_AT plumbing itself broke.

Net: 37 -> 40 cells (txn_isolated's split adds 3: 2 new GREEN atomicity
cells on prod_s1/prod_s4, 1 new RED atomicity cell on legacy_yes_s1).
27 -> 29 GREEN by default, 10 -> 11 RED (gated). Full default run:
40 passed, 0 failed. RED=1 run: 31 passed, 9 failed (the 2 probabilistic
checkpoint-Finalize cells did not trigger in this single-iteration
sample, as documented). 3x-consecutive re-verification on the VM:
kv_spilled_isolated passes with a genuine spill at both shard counts;
txn_isolated_atomicity is GREEN 3x on prod_s1/prod_s4 and RED 3x on
legacy_yes_s1.

Test-only; no production code changed in this stage.

author: Tin Dang
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40d73bde-c92b-417b-914c-2c674edbc545

📥 Commits

Reviewing files that changed from the base of the PR and between 6ccfb88 and d3d3b8e.

📒 Files selected for processing (2)
  • tests/common/mod.rs
  • tests/crash_matrix_cross_plane/scenarios.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/crash_matrix_cross_plane/scenarios.rs

📝 Walkthrough

Walkthrough

Adds a feature-gated kill-9 crash/recovery matrix covering multiple persistence planes, mixed workloads, durability markers, no-durability configurations, seeded regressions, and legacy, production, and spot configurations.

Changes

Cross-plane crash recovery

Layer / File(s) Summary
Test foundation and RESP client
tests/common/mod.rs, tests/crash_matrix_cross_plane.rs, tests/crash_matrix_cross_plane/resp.rs
Adds binary discovery, process termination, port shutdown polling, feature-gated module wiring, soak iteration parsing, and a buffered RESP2 client.
Crash harness and durability polling
tests/crash_matrix_cross_plane/harness.rs
Defines server configurations, process lifecycle management, WAL/AOF marker polling, checkpoint synchronization, spill-file detection, RED gating, and timing jitter.
Plane helpers and mixed workloads
tests/crash_matrix_cross_plane/planes.rs, tests/crash_matrix_cross_plane/mixed.rs
Adds KV, Graph, Vector, WS, MQ, transaction, synchronized mixed-workload, burst-workload, and recovered-truth helpers.
Recovery scenarios
tests/crash_matrix_cross_plane/scenarios.rs
Adds isolated-plane, transaction, mixed-workload, no-durability, checkpoint, WAL-pressure, and corruption-tolerance crash/restart scenarios.
Matrix entrypoints and documentation
tests/crash_matrix_cross_plane/tests_*.rs, CHANGELOG.md
Adds legacy, production, seeded-RED, and spot-check test dispatchers, plus documentation of matrix scope and gating behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TestScenario
  participant MoonServer
  participant DurabilityFiles
  participant RestartedServer
  TestScenario->>MoonServer: start configured instance
  TestScenario->>MoonServer: execute plane or mixed workload
  MoonServer->>DurabilityFiles: write WAL/AOF markers
  TestScenario->>DurabilityFiles: poll for durability boundary
  TestScenario->>MoonServer: SIGKILL
  TestScenario->>RestartedServer: restart with test directory
  TestScenario->>RestartedServer: verify recovered state
Loading

Possibly related PRs

  • pilotspace/moon#178: The new WAL/AOF recovery assertions exercise durability behavior introduced by the WAL group-commit changes.
  • pilotspace/moon#229: Both changes include MOON_BIN and CARGO_BIN_EXE_moon binary-resolution behavior.
  • pilotspace/moon#291: The seeded MQ crash-recovery tests exercise durable MQ WAL replay behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the crash-matrix suite and its kernel M3 stage 1 scope.
Description check ✅ Passed The description is detailed and on-topic, but it omits the template's Checklist, Performance Impact, and Notes headings.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/kernel-m3-g1-crash-matrix

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
tests/crash_matrix_cross_plane/planes.rs (1)

108-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate reply-parsing between graph_query_ids and graph_query_ids_valid_at.

The two functions differ only in the extra VALID_AT <ms> argument; the entire nested Array → rows → cells → Int/Bulk decode is copy-pasted. Extracting a shared parser removes ~25 duplicated lines and avoids the two copies drifting apart if the row-shape decoding ever changes.

♻️ Sketch: shared parser
+fn parse_graph_query_ids(reply: Resp, cypher: &str, ctx: &str) -> BTreeSet<i64> {
+    match reply {
+        Resp::Array(Some(outer)) => match outer.get(1) {
+            Some(Resp::Array(Some(rows))) => rows
+                .iter()
+                .map(|r| match r {
+                    Resp::Array(Some(cells)) => match cells.first() {
+                        Some(Resp::Int(i)) => *i,
+                        Some(Resp::Bulk(Some(b))) => {
+                            String::from_utf8_lossy(b).parse().expect("int cell")
+                        }
+                        other => panic!("unexpected cell: {other:?}"),
+                    },
+                    other => panic!("malformed row: {other:?}"),
+                })
+                .collect(),
+            other => panic!("malformed query reply, rows slot = {other:?}"),
+        },
+        Resp::Error(e) => panic!("query {cypher:?} {ctx}errored: {e}"),
+        other => panic!("malformed query reply: {other:?}"),
+    }
+}

Then both helpers become thin wrappers around c.cmd_s(...) + parse_graph_query_ids(...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/crash_matrix_cross_plane/planes.rs` around lines 108 - 163, Extract the
shared nested response decoding from graph_query_ids and
graph_query_ids_valid_at into a dedicated parse_graph_query_ids helper. Keep the
existing Array/rows/cells Int-or-Bulk handling and malformed-response panics in
that parser, then have both query functions call it after their respective
c.cmd_s invocations while retaining their distinct query error context.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/common/mod.rs`:
- Around line 171-188: Update both cfg-specific sigkill implementations into one
platform-independent helper that calls Child::kill() and then waits via
Child::wait(). Remove the raw libc::kill invocation, unsafe block, Unix
conditional compilation, and unused pid handling.

In `@tests/crash_matrix_cross_plane/scenarios.rs`:
- Around line 881-885: Replace the vacuous as_int_or_zero(... ) >= 0 assertion
in the MQ validation block with a direct Resp check, reusing the nearby
is_unexpected_plane_error helper if appropriate. Ensure an unexpected XLEN error
fails the assertion while allowing a missing queue’s Int(0) response.

---

Nitpick comments:
In `@tests/crash_matrix_cross_plane/planes.rs`:
- Around line 108-163: Extract the shared nested response decoding from
graph_query_ids and graph_query_ids_valid_at into a dedicated
parse_graph_query_ids helper. Keep the existing Array/rows/cells Int-or-Bulk
handling and malformed-response panics in that parser, then have both query
functions call it after their respective c.cmd_s invocations while retaining
their distinct query error context.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a0efd16-bf68-460b-af24-b33b6b920fc0

📥 Commits

Reviewing files that changed from the base of the PR and between d019dc7 and 6ccfb88.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • tests/common/mod.rs
  • tests/crash_matrix_cross_plane.rs
  • tests/crash_matrix_cross_plane/harness.rs
  • tests/crash_matrix_cross_plane/mixed.rs
  • tests/crash_matrix_cross_plane/planes.rs
  • tests/crash_matrix_cross_plane/resp.rs
  • tests/crash_matrix_cross_plane/scenarios.rs
  • tests/crash_matrix_cross_plane/tests_legacy.rs
  • tests/crash_matrix_cross_plane/tests_prod.rs
  • tests/crash_matrix_cross_plane/tests_seeded_red.rs
  • tests/crash_matrix_cross_plane/tests_spot.rs

Comment thread tests/common/mod.rs Outdated
Comment thread tests/crash_matrix_cross_plane/scenarios.rs
…us MQ burst assertion

tests/common::sigkill: Child::kill() is documented to send SIGKILL on Unix,
so the raw libc::kill unsafe block and the unix/non-unix cfg split were
unnecessary — one safe implementation, same semantics.

concurrent_burst MQ check: `as_int_or_zero(XLEN) >= 0` was vacuous — error
replies mapped to 0 and the assertion could never fail, silently passing a
genuinely corrupted MQ plane. Now asserts the reply is not an unexpected
plane error (XLEN on a never-durable queue replies Int(0), so no benign
not-found allowlist is needed). Removed the now-unused as_int_or_zero.

author: Tin Dang
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants