tursodatabase/turso · error

Invalid btree seek state in seek_btree_and_set_peek: {:?}

Error message

Invalid btree seek state in seek_btree_and_set_peek: {:?}

What it means

seek_btree_and_set_peek drives the btree half of the dual (btree + MVCC) seek in a loop that requires self.state to be Seek(SeekBtree(..), direction) at the top of every iteration. Any other state on entry or after a mutation means the state machine advanced wrongly, and it panics with the state dumped for diagnosis.

Source

Thrown at core/mvcc/cursor.rs:1135

        self.index_finger.reset();
    }

    /// Seek btree cursor and set btree_peek to the result.
    /// Skips rows that are shadowed by MVCC.
    /// Returns IOResult indicating if we need to yield for IO or are done.
    fn seek_btree_and_set_peek(&mut self, seek_key: SeekKey<'_>, op: SeekOp) -> IOResultOr<()> {
        // Fast path: btree not allocated
        if !self.is_btree_allocated() {
            self.dual_peek.btree_peek = CursorPeek::Exhausted;
            self.state = None;
            return Ok(IOResult::Done(()));
        }

        loop {
            let Some(MvccLazyCursorState::Seek(SeekState::SeekBtree(btree_seek_state), direction)) =
                self.state.clone()
            else {
                panic!(
                    "Invalid btree seek state in seek_btree_and_set_peek: {:?}",
                    self.state
                );
            };
            match btree_seek_state {
                SeekBtreeState::SeekBtree => {
                    let seek_result = return_if_io!(self.btree_cursor.seek(seek_key.clone(), op));

                    match seek_result {
                        SeekResult::NotFound => {
                            self.dual_peek.btree_peek = CursorPeek::Exhausted;
                            return Ok(IOResult::Done(()));
                        }
                        SeekResult::TryAdvance => {
                            // Need to advance to find actual matching entry
                            self.state.replace(MvccLazyCursorState::Seek(
                                SeekState::SeekBtree(SeekBtreeState::AdvanceBTree),
                                direction,

View on GitHub (pinned to 492c4a71cd)

Solutions

  1. If contributing: verify every IO suspension inside the loop restores Seek(SeekBtree(..), dir) before re-entering
  2. Reduce to a minimal query using an index under MVCC and report, including the panic state dump
  3. Avoid the specific seek pattern (mixed ASC/DESC index scans) until fixed
Defensive patterns

Strategy: validation

Validate before calling

debug_assert!(
    matches!(self.state, Some(MvccLazyCursorState::Seek(SeekState::SeekBtree(_), _))),
    "seek state corrupted before btree seek continuation"
);

Prevention

When it happens

Trigger: Internal: seek code paths that mutate self.state mid-seek (IO suspension and re-entry via IOResult::IO, rewinds, direction switches) leaving a non-SeekBtree state while the loop continues.

Common situations: Re-entrancy after async IO suspension inside MVCC seeks; contributor changes to SeekState transitions.

Related errors


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