@@ -506,6 +506,14 @@ pub enum CursorValidState {
506506 RequireAdvance ( IterationDirection ) ,
507507}
508508
509+ #[ derive( Debug , Clone ) ]
510+ struct PendingChild {
511+ page_id : i64 ,
512+ push_backwards : bool ,
513+ set_cell_index : Option < i32 > ,
514+ retreat_parent : bool ,
515+ }
516+
509517#[ derive( Debug ) ]
510518/// State used for seeking
511519pub enum CursorSeekState {
@@ -674,6 +682,8 @@ pub struct BTreeCursor {
674682 /// Advancing is only skipped if the cursor is currently pointing to a valid record
675683 /// when next() is called.
676684 pub skip_advance : Cell < bool > ,
685+ /// Pending child page read that must complete before mutating the stack (keeps loops re-entrant on IO).
686+ pending_child : RefCell < Option < PendingChild > > ,
677687}
678688
679689/// We store the cell index and cell count for each page in the stack.
@@ -699,6 +709,40 @@ impl BTreeNodeState {
699709}
700710
701711impl BTreeCursor {
712+ /// Load a pending child page if present, applying deferred stack mutations only after the read completes.
713+ fn load_pending_child ( & mut self ) -> Result < IOResult < ( ) > > {
714+ let Some ( pending) = self . pending_child . borrow_mut ( ) . take ( ) else {
715+ return Ok ( IOResult :: Done ( ( ) ) ) ;
716+ } ;
717+
718+ match self . read_page ( pending. page_id ) ? {
719+ IOResult :: Done ( ( page, c) ) => {
720+ if let Some ( c) = c {
721+ if !c. succeeded ( ) {
722+ * self . pending_child . borrow_mut ( ) = Some ( pending) ;
723+ return Ok ( IOResult :: IO ( IOCompletions :: Single ( c) ) ) ;
724+ }
725+ }
726+ if pending. retreat_parent {
727+ self . stack . retreat ( ) ;
728+ }
729+ if let Some ( idx) = pending. set_cell_index {
730+ self . stack . set_cell_index ( idx) ;
731+ }
732+ if pending. push_backwards {
733+ self . stack . push_backwards ( page) ;
734+ } else {
735+ self . stack . push ( page) ;
736+ }
737+ Ok ( IOResult :: Done ( ( ) ) )
738+ }
739+ IOResult :: IO ( c) => {
740+ * self . pending_child . borrow_mut ( ) = Some ( pending) ;
741+ Ok ( IOResult :: IO ( c) )
742+ }
743+ }
744+ }
745+
702746 pub fn new ( pager : Arc < Pager > , root_page : i64 , num_columns : usize ) -> Self {
703747 let valid_state = if root_page == 1 && !pager. db_state . get ( ) . is_initialized ( ) {
704748 CursorValidState :: Invalid
@@ -741,6 +785,7 @@ impl BTreeCursor {
741785 seek_end_state : SeekEndState :: Start ,
742786 move_to_state : MoveToState :: Start ,
743787 skip_advance : Cell :: new ( false ) ,
788+ pending_child : RefCell :: new ( None ) ,
744789 }
745790 }
746791
@@ -802,6 +847,9 @@ impl BTreeCursor {
802847 #[ instrument( skip( self ) , level = Level :: DEBUG , name = "prev" ) ]
803848 pub fn get_prev_record ( & mut self ) -> Result < IOResult < bool > > {
804849 loop {
850+ if let IOResult :: IO ( c) = self . load_pending_child ( ) ? {
851+ return Ok ( IOResult :: IO ( c) ) ;
852+ }
805853 let ( old_top_idx, page_type, is_index, is_leaf, cell_count) = {
806854 let page = self . stack . top_ref ( ) ;
807855 let contents = page. get_contents ( ) ;
@@ -822,12 +870,12 @@ impl BTreeCursor {
822870 let rightmost_pointer = self . stack . top_ref ( ) . get_contents ( ) . rightmost_pointer ( ) ;
823871 if let Some ( rightmost_pointer) = rightmost_pointer {
824872 let past_rightmost_pointer = cell_count as i32 + 1 ;
825- self . stack . set_cell_index ( past_rightmost_pointer ) ;
826- let ( page , c ) = return_if_io ! ( self . read_page ( rightmost_pointer as i64 ) ) ;
827- self . stack . push_backwards ( page ) ;
828- if let Some ( c ) = c {
829- io_yield_one ! ( c ) ;
830- }
873+ * self . pending_child . borrow_mut ( ) = Some ( PendingChild {
874+ page_id : rightmost_pointer as i64 ,
875+ push_backwards : true ,
876+ set_cell_index : Some ( past_rightmost_pointer ) ,
877+ retreat_parent : false ,
878+ } ) ;
831879 continue ;
832880 }
833881 }
@@ -892,7 +940,14 @@ impl BTreeCursor {
892940 // this parent: key 666
893941 // left child has: key 663, key 664, key 665
894942 // we need to move to the previous parent (with e.g. key 662) when iterating backwards.
895- self . stack . retreat ( ) ;
943+ // Defer retreat until after the child page load completes to keep this re-entrant.
944+ self . pending_child . borrow_mut ( ) . replace ( PendingChild {
945+ page_id : left_child_page as i64 ,
946+ push_backwards : true ,
947+ set_cell_index : None ,
948+ retreat_parent : true ,
949+ } ) ;
950+ continue ;
896951 }
897952
898953 let ( mem_page, c) = return_if_io ! ( self . read_page( left_child_page as i64 ) ) ;
@@ -946,13 +1001,21 @@ impl BTreeCursor {
9461001 * remaining_to_read -= to_read;
9471002
9481003 if * remaining_to_read != 0 && next != 0 {
949- let ( new_page, c) = return_if_io ! ( self . pager. read_page( next as i64 ) ) ;
950- * page = new_page;
951- * next_page = next;
952- if let Some ( c) = c {
953- io_yield_one ! ( c) ;
1004+ match self . pager . read_page ( next as i64 ) ? {
1005+ IOResult :: Done ( ( new_page, c) ) => {
1006+ * page = new_page;
1007+ * next_page = next;
1008+ if let Some ( c) = c {
1009+ io_yield_one ! ( c) ;
1010+ }
1011+ continue ;
1012+ }
1013+ IOResult :: IO ( c) => {
1014+ // Preserve progress and retry after IO completes.
1015+ * next_page = next;
1016+ return Ok ( IOResult :: IO ( c) ) ;
1017+ }
9541018 }
955- continue ;
9561019 }
9571020 turso_assert ! (
9581021 * remaining_to_read == 0 && next == 0 ,
0 commit comments