fix(cubesys): make transaction commits durable + replayable

Two correctness bugs found via ad-hoc daemon verification (T5 was
compile-verified only before):

1. decode_wal dropped WalOp::Txn entries on replay: the txn encoder emits
   {"seq","op":"txn","batch"} with NO c/z/y/x fields, but decode_wal
   read c/z/y/x unconditionally -> field_u8("c") returned Err -> the
   whole entry was skipped. Committed transactions silently vanished on
   restart. Fix: branch on op=='txn' before the c/z/y/x extraction.

2. commit was not synchronously durable: append_txn only buffered to the
   WAL pending buffer; fsync happened on the 25ms group thread. A
   clean stop within that window lost the commit. Fix: commit_txn now
   calls wal.flush_pending() (fsync) before returning, so COMMIT is
   durable on return -- a real transaction boundary.

Adds unit test commit_replays_from_wal_without_checkpoint (would have
failed before fix 1). Ad-hoc verifier exercises all 3 changed paths on
the live cube-server socket.
This commit is contained in:
CUBELinux-2
2026-08-11 13:09:24 -04:00
parent 308e20852c
commit c36f64c78d
2 changed files with 59 additions and 12 deletions
+39
View File
@@ -652,4 +652,43 @@ mod tests {
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn commit_replays_from_wal_without_checkpoint() {
// The daemon never calls checkpoint() between a commit and a crash; it
// relies on WAL replay. This must hold WITHOUT an explicit checkpoint()
// — i.e. the WalOp::Txn entry must round-trip through decode_wal/encode_wal.
let dir = std::env::temp_dir().join(format!("cube2-txn-wal-{}", std::process::id()));
let _ = std::fs::create_dir_all(&dir);
let db = dir.join("db.cubedb");
let wal = dir.join("wal.ndjson");
let rec = dir.join("recovery.jsonl");
let _ = std::fs::remove_file(&db);
let _ = std::fs::remove_file(&wal);
let cs = ConcurrentStore::open(
db.to_str().unwrap(),
wal.to_str().unwrap(),
rec.to_str().unwrap(),
DurabilityConfig::default(),
)
.unwrap();
let mut s = Session::with_store(Arc::new(cs));
s.exec("begin").unwrap();
s.exec("prog /c005/z001/y001/x001 const 9 halt").unwrap();
s.exec("commit").unwrap();
drop(s); // NO checkpoint() — pure WAL replay on reopen
let cs2 = ConcurrentStore::open(
db.to_str().unwrap(),
wal.to_str().unwrap(),
rec.to_str().unwrap(),
DurabilityConfig::default(),
)
.unwrap();
assert!(
cs2.get_raw(&Czyx::new(5, 1, 1, 1)).is_some(),
"committed txn must replay from WAL even without a checkpoint"
);
let _ = std::fs::remove_dir_all(&dir);
}
}