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

  1. If you provide a custom IO implementation, always record the error on the Completion before completing it on failure
  2. For built-in IO backends, treat occurrences as an engine bug: report the IO backend and system state
  3. Fix the underlying storage issue (disk health, permissions) and retry
  4. 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

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


AI-assisted analysis of tursodatabase/turso@492c4a71cd (2026-08-20). Data as JSON: /api/errors/e0e8dbe84f50d74e. Report an issue: GitHub.