tursodatabase/turso · critical
finished unsuccessful cacheflush read must have an error
Error message
finished unsuccessful cacheflush read must have an error
What it means
Cache-flush state machine (core/storage/pager.rs): when a page-read Completion reports finished-but-unsuccessful, the code retrieves the error with completion.get_error().expect("finished unsuccessful cacheflush read must have an error"). The completion contract is finished + !succeeded implies an error was recorded; a completion that finishes failed without an error payload violates it.
Source
Thrown at core/storage/pager.rs:4048
// All pages collected and written
Ok(CacheFlushStep::Done(state.completions))
}
/// Handle completion of async page read for evicted page.
fn cacheflush_handle_read(
&self,
wal: &Arc<dyn Wal>,
page_sz: PageSize,
mut state: CollectingState,
page_id: usize,
page: PageRef,
completion: Completion,
) -> Result<CacheFlushStep> {
if !completion.succeeded() {
if completion.finished() {
let err = completion
.get_error()
.expect("finished unsuccessful cacheflush read must have an error");
return Err(err.into());
}
return Ok(CacheFlushStep::Yield(
CacheFlushState::WaitingForRead {
state,
page_id,
page,
completion: completion.clone(),
},
IOCompletions(completion),
));
}
trace!(
"cacheflush(page={}, page_type={:?}) [re-read complete]",
page_id,
page.get_contents().page_type().ok()
);
state.collected_pages.push(page);View on GitHub (pinned to 492c4a71cd)
Solutions
- If you provide a custom IO implementation, always record the error on the Completion before completing it on failure
- For built-in IO backends, treat occurrences as an engine bug: report the IO backend and system state
- Fix the underlying storage issue (disk health, permissions) and retry
- Run PRAGMA integrity_check afterwards to verify database pages
Example fix
// before (custom IO implementation)
let completion = Completion::new_read(|_| {});
complete_failed_without_error(completion); // finishes, !succeeded, no error -> engine panics
// after
let completion = Completion::new_read(|_| {});
completion.set_error(CompletionError::IOError(errno));
complete(completion); Defensive patterns
Strategy: try-catch
Try / catch
let result = std::panic::catch_unwind(AssertUnwindSafe(|| do_heavy_read_workload(&conn)));
if result.is_err() {
conn.close().ok();
// Check storage health, then reopen and verify:
// PRAGMA integrity_check;
} Prevention
- If you implement the IO trait, always record an error on the Completion before completing it in failure paths
- Surface and log IO-level errors from your storage layer instead of swallowing them
- Monitor disk health; real read failures underlie most completions finishing unsuccessfully
- Run integrity checks after IO errors to detect damaged pages
When it happens
Trigger: A real IO error during a cache-flush page read (disk read failure, bad sector, IO driver fault) where the Completion object reached the finished state without storing its error.
Common situations: Custom IO implementations with buggy completion handling (finishing without set_error), OS-level read errors surfacing mid-flush, engine regressions in Completion plumbing.
Related errors
- buffer not loaded
- DB should not be initialized and should not do any IO
- Page size too small, a ptrmap page cannot map any db pages.
- subjournal must be opened
- invalid page size stored
AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20).
Data as JSON: /api/errors/e0e8dbe84f50d74e.
Report an issue: GitHub.