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
@@ -47,6 +47,11 @@ pub struct SqliteTxHandle {
|
||||
conn: Option<rusqlite::Connection>,
|
||||
writer: Arc<Writer>,
|
||||
reopen: Arc<dyn Fn() -> Result<rusqlite::Connection> + Send + Sync>,
|
||||
/// This store's commit-fault arm (`sqlite-commit-error-arm`) —
|
||||
/// read once per commit by the test-only fault branch; dead in
|
||||
/// production builds.
|
||||
#[cfg(test)]
|
||||
commit_fault: crate::seam::commit_fault::Flag,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SqliteTxHandle {
|
||||
@@ -61,6 +66,7 @@ impl SqliteTxHandle {
|
||||
pub(crate) fn begin(
|
||||
writer: Arc<Writer>,
|
||||
reopen: Arc<dyn Fn() -> Result<rusqlite::Connection> + Send + Sync>,
|
||||
#[cfg(test)] commit_fault: crate::seam::commit_fault::Flag,
|
||||
) -> BoxedFuture<'static, Result<Box<dyn TxHandle + Send>>> {
|
||||
Box::pin(async move {
|
||||
let writer2 = writer.clone();
|
||||
@@ -83,6 +89,8 @@ impl SqliteTxHandle {
|
||||
conn: Some(conn),
|
||||
writer,
|
||||
reopen,
|
||||
#[cfg(test)]
|
||||
commit_fault,
|
||||
}) as _)
|
||||
})
|
||||
}
|
||||
@@ -392,9 +400,19 @@ impl TxHandle for SqliteTxHandle {
|
||||
};
|
||||
let writer = self.writer.clone();
|
||||
let writer_reopen = self.reopen.clone();
|
||||
#[cfg(test)]
|
||||
let commit_fault = self.commit_fault.clone();
|
||||
Box::pin(async move {
|
||||
blocking(
|
||||
move || match conn.execute_batch("COMMIT").map_err(sqlite_error) {
|
||||
blocking(move || {
|
||||
#[cfg(test)]
|
||||
let committed = if crate::seam::commit_fault::take(&commit_fault) {
|
||||
Err(crate::seam::commit_fault::failure())
|
||||
} else {
|
||||
conn.execute_batch("COMMIT")
|
||||
};
|
||||
#[cfg(not(test))]
|
||||
let committed = conn.execute_batch("COMMIT");
|
||||
match committed.map_err(sqlite_error) {
|
||||
Ok(()) => {
|
||||
writer.release(conn);
|
||||
Ok(())
|
||||
@@ -406,8 +424,8 @@ impl TxHandle for SqliteTxHandle {
|
||||
}
|
||||
Err(e)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user