tursodatabase/turso · error

page should be present

Error message

page should be present

What it means

Inside integrity_check()'s page-walk loop (core/storage/btree.rs): when state.page is None on re-entry, the loop reads the page, stores it in state.page, yields any read completion, then immediately takes it back with expect("page should be present"). Because the take happens right after the store in the same basic block, this assert is effectively a defensive self-check for state-machine re-entry bugs.

Source

Thrown at core/storage/btree.rs:8088

        else {
            return Ok(IOResult::Done(()));
        };
        turso_assert!(
            page_idx >= 0,
            "pages should be positive during integrity check"
        );
        let page = match state.page.take() {
            Some(page) => page,
            None => {
                // On `IO(spill_c)` we leave `state.page = None` so re-entry
                // re-takes this None branch and resumes via the pager's
                // `pending_reads` memoization.
                let (page, c) = return_if_io!(pager.read_page(page_idx));
                state.page = Some(page);
                if let Some(c) = c {
                    io_yield_one!(c);
                }
                state.page.take().expect("page should be present")
            }
        };
        turso_assert!(page.is_loaded(), "page should be loaded");
        state.page_stack.pop();

        let contents = page.get_contents();
        if page_category == PageCategory::FreeListTrunk {
            state.freelist_count.actual_count += 1;
            let next_freelist_trunk_page =
                contents.read_u32_no_offset(FREELIST_TRUNK_OFFSET_NEXT_TRUNK_PTR);
            if next_freelist_trunk_page != 0 {
                if next_freelist_trunk_page as usize > state.db_size {
                    tracing::error!(
                        "integrity_check: freelist trunk page {} has invalid next pointer {}. header_bytes={:02x?}",
                        page.get().id(),
                        next_freelist_trunk_page,
                        &contents.as_ptr()[0..16]
                    );

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. Rerun the integrity check on a copy of the database with a single connection to rule out re-entrancy
  2. Report to Turso if it reproduces - it marks a state-machine re-entry bug, not data corruption per se
  3. Run the check on an older/newer engine build to bisect a regression
  4. Verify the database afterwards with sqlite3's PRAGMA integrity_check as a cross-check
Defensive patterns

Strategy: validation

Validate before calling

// Run the integrity check single-threaded on a copy first - the assert guards
// against re-entrant state-machine use, so eliminate concurrency:
let copy = std::path::Path::new("/tmp/check-copy.db");
std::fs::copy(&db_path, copy)?;
let conn = Connection::open(copy)?;
let report = conn.query_row("PRAGMA integrity_check", [], |r| r.get::<_, String>(0))?;

Try / catch

let result = std::panic::catch_unwind(AssertUnwindSafe(|| run_integrity_check(&conn)));
match result {
    Ok(Ok(report)) => /* inspect report */,
    Ok(Err(e)) => /* surfaced DB error */,
    Err(_) => /* engine state bug: report upstream, cross-check with sqlite3 CLI */,
}

Prevention

When it happens

Trigger: Running PRAGMA integrity_check (which also validates the freelist) on a database with freelist trunk/leaf pages. The expect could only fire if state.page were cleared between the store and the take - i.e. concurrent re-entry of the integrity-check state machine.

Common situations: Integrity checks on large or spilled databases where reads yield IO; engine regressions in integrity-check re-entrancy.

Related errors


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