SQLite commit-error-arm coverage (task sqlite-commit-error-arm): the wave-3 review gate's deferred test landed — failed_commit_replenishes_the_writer_slot drives a failing COMMIT through the engine's commit path and pins the review's code-read fix end-to-end: the error surfaces as the opaque Database carrying the SQLITE_FULL-shaped source chain, the failed connection is dropped and the writer slot replenished via the handle's reopen closure (the next begin_tx proceeds within a bounded timeout, no parking — the store-wide-livelock posture), no partial-commit residue (the dropped connection's uncommitted writes read back None), the post-failure commit is a real clean commit (the fault disarms on consumption), and auto-commit notify works afterward. Injection is a cfg(test) commit-fault seam in seam.rs — a per-store Arc<AtomicBool> arm (born disarmed, armed via arm_commit_fault, take() disarms on first consumption so exactly one commit faults) whose fabricated rusqlite SqliteFailure feeds the production commit error arm rather than replicating it; the PRAGMA max_page_count route was probed live against both WAL and DELETE journal modes first and rejected: SQLite checks the page-count limit at page-allocation time, so the squeeze always fails the growth statement (SQLITE_FULL/DiskFull on the first INSERT) and leaves no transaction active for COMMIT to fail — the arm is unreachable through PRAGMA-space (also probed: the pragma is per-connection, so pre-begin arming on the writer conn would have ridden into the tx conn; the route failed on error placement, not delivery). Mechanism choice and probes documented in the seam doc comment and the task Notes. Cross-test safety is per-store scoping; parallel stores never see the arm. Replay-proofed live: with the error arm's writer_reopen replenish temporarily removed the test fails (begin_tx parks past the 5 s timeout — the stranding the review identified), reverted it passes. Plumbing follows the pg-fix-forwarder-reconnect cfg(test) precedent: fields on SqliteStore/SqliteTxHandle and a begin param are cfg-gated, production builds compile the plain path. The waves-1-2 review's optional watcher reconnect-success add rides here (taken — recorded in Notes): reconnect_success_resumes_wake_delivery drives run_poll_loop through its existing open_conn_fn seam (same instrument as the W-1 failure test), with the db file present throughout because the vanished-file route cannot reach the success body (file reappearance trips the dead-man's identity switch first): initial open + first two reconnects fail by injection, the third reconnect succeeds, and a subsequent commit wakes on_change — the success arm's data_version re-baseline and restored delivery pinned. Watcher shape untouched. Verified: cargo test -p alkstore-sqlite green server-less (191 lib + 25 suite), workspace cargo test 399/0, clippy -D warnings, fmt clean
This commit is contained in:
1 parent
36023914b2
commit
0c7744977c
6 files changed
+320
-8
No files matched your search
@@ -1199,6 +1199,74 @@ async fn cancelling_a_tx_future_rolls_back() {
|
||||
cleanup(&dir);
|
||||
}
|
||||
|
||||
/// Acceptance: a failing `COMMIT` surfaces as the opaque `Database`
|
||||
/// (`sqlite-commit-error-arm`, the wave-3 review's deferred coverage —
|
||||
/// the replenish fix pinned by code-read there, now test-exercised),
|
||||
/// does not strand the writer slot — the failed connection is dropped
|
||||
/// (its state unknowable) and the slot replenished via the handle's
|
||||
/// `reopen` closure — and the store stays fully usable: the next
|
||||
/// `begin_tx` proceeds, finds no partial-commit residue (the failed
|
||||
/// tx's writes rolled back with the dropped connection), commits
|
||||
/// cleanly, and auto-commit ops work.
|
||||
///
|
||||
/// Injection is the engine's `cfg(test)` commit-fault seam
|
||||
/// (seam.rs): a `SQLITE_FULL`-shaped driver error substituted for one
|
||||
/// `COMMIT`, feeding the production error arm (drop + `reopen`
|
||||
/// replenish) rather than fabricating its behavior. The seam over the
|
||||
/// `PRAGMA max_page_count` squeeze: live probes of both WAL and
|
||||
/// DELETE journal modes showed SQLite checks the page-count limit at
|
||||
/// page-allocation time — the squeeze always fails the growth
|
||||
/// statement (first `INSERT`) and leaves no transaction for `COMMIT`
|
||||
/// to fail on, so the PRAGMA route cannot reach this arm
|
||||
/// deterministically. The seam carries no cross-test risk: the flag
|
||||
/// is per-store and disarms on its first consumption.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn failed_commit_replenishes_the_writer_slot() {
|
||||
let dir = temp_dir("commit-err");
|
||||
let concrete = open_store(dir.join("store.db").to_str().unwrap(), Default::default()).unwrap();
|
||||
|
||||
concrete.arm_commit_fault();
|
||||
|
||||
let mut tx = concrete.begin_tx().await.unwrap();
|
||||
let id = tx
|
||||
.enqueue_tx("q", EnqueueOpts::default(), serde_json::json!({"n": 1}))
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(id > 0, "the in-tx write landed before the failed commit");
|
||||
|
||||
let err = tx.commit().await.unwrap_err();
|
||||
match err {
|
||||
Error::Database(source) => {
|
||||
assert!(
|
||||
source.to_string().contains("full"),
|
||||
"the commit error carries the SQLITE_FULL-shaped source chain: {source}"
|
||||
);
|
||||
}
|
||||
other => panic!("a failed commit errs opaque Database, got {other:?}"),
|
||||
}
|
||||
|
||||
let next = tokio::time::timeout(std::time::Duration::from_secs(5), concrete.begin_tx())
|
||||
.await
|
||||
.expect("the slot must be grantable after the failed commit (replenished via reopen)")
|
||||
.unwrap();
|
||||
let mut next = next;
|
||||
let residue = next.get_job_tx("q", id).await.unwrap();
|
||||
assert_eq!(
|
||||
residue, None,
|
||||
"the failed commit's writes left no partial-commit residue"
|
||||
);
|
||||
next.commit()
|
||||
.await
|
||||
.expect("the post-failure commit must be a real, clean commit (fault disarmed)");
|
||||
|
||||
concrete
|
||||
.notify("probe", serde_json::json!({"after": "commit-error"}))
|
||||
.await
|
||||
.expect("auto-commit ops work after the failed commit");
|
||||
drop(concrete);
|
||||
cleanup(&dir);
|
||||
}
|
||||
|
||||
/// Acceptance: a tokio task cancelled *mid-op* (its future dropped
|
||||
/// while an op's blocking round trip is in flight) cannot strand the
|
||||
/// writer slot — the op's RAII connection lease replenishes the slot
|
||||
|
||||
Reference in new issue
Block a user