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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user