33use std:: sync:: Arc ;
44
55use crate :: Result ;
6- use crate :: errors:: { CorruptionDetail , PagedbError } ;
6+ use crate :: errors:: PagedbError ;
77use crate :: vfs:: Vfs ;
88
99use crate :: btree:: leaf:: { Leaf , LeafValue } ;
@@ -49,15 +49,28 @@ impl<V: Vfs> BTree<V> {
4949 }
5050 }
5151
52+ /// Return the pages of a chain that was written for a value the caller
53+ /// never got to commit.
54+ ///
55+ /// Best-effort by design: this runs on an error path, and a failure here
56+ /// must not replace the error the caller actually needs to see. It is
57+ /// logged rather than swallowed, because the cost of giving up is a page
58+ /// leak that nothing else will notice.
5259 async fn discard_uncommitted_value ( & mut self , value : Option < LeafValue > ) {
53- if let Some ( LeafValue :: Overflow { root_page_id, .. } ) = value {
54- if let Ok ( page_ids) =
55- overflow:: collect_chain ( & self . pager , self . realm_id , root_page_id) . await
56- {
60+ let Some ( LeafValue :: Overflow { root_page_id, .. } ) = value else {
61+ return ;
62+ } ;
63+ match overflow:: collect_chain ( & self . pager , self . realm_id , root_page_id) . await {
64+ Ok ( page_ids) => {
5765 for page_id in page_ids {
5866 self . free_page ( page_id) ;
5967 }
6068 }
69+ Err ( error) => tracing:: warn!(
70+ root_page_id,
71+ error = %error,
72+ "could not reclaim the overflow chain of an uncommitted value; its pages are leaked"
73+ ) ,
6174 }
6275 }
6376
@@ -236,7 +249,11 @@ impl<V: Vfs> BTree<V> {
236249 /// # Errors
237250 ///
238251 /// Returns [`PagedbError::AppendNotMonotonic`] if `key` is not strictly
239- /// greater than the previously-appended key in this `BTree` session.
252+ /// greater than the previously-appended key in this `BTree` session, or —
253+ /// on the first append of a session, when there is no such key yet — not
254+ /// strictly greater than the largest key already in the tree. Both are the
255+ /// same invariant: the cached rightmost path is only meaningful while every
256+ /// append lands to the right of everything before it.
240257 /// Intended for op-logs, time-series indexes, FTS posting-list builds —
241258 /// any workload where the embedder can guarantee monotonic-key insert.
242259 pub async fn put_append ( & mut self , key : & [ u8 ] , value : & [ u8 ] ) -> Result < ( ) > {
@@ -262,11 +279,12 @@ impl<V: Vfs> BTree<V> {
262279 }
263280
264281 // Fast path: cached rightmost path is valid → skip descent.
265- let path = if let Some ( cached) = self . append_cached_path . take ( ) {
266- cached
267- } else {
268- self . path_to_rightmost_leaf ( ) . await ?
282+ let path = match & self . append_cached_path {
283+ Some ( cached) => cached. clone ( ) ,
284+ None => self . path_to_rightmost_leaf ( ) . await ?,
269285 } ;
286+ // Checked before the cached path is consumed: a rejected append must
287+ // leave the session exactly as it found it, cache included.
270288 if self . append_last_key . is_none ( )
271289 && self
272290 . max_key_at_path ( & path)
@@ -275,6 +293,7 @@ impl<V: Vfs> BTree<V> {
275293 {
276294 return Err ( PagedbError :: AppendNotMonotonic ) ;
277295 }
296+ self . append_cached_path = None ;
278297 let leaf_value = self . leaf_value_for ( value) . await ?;
279298 let path_for_retry = path. clone ( ) ;
280299 let split = self . put_at_path ( path, key, leaf_value) . await ?;
@@ -394,7 +413,7 @@ impl<V: Vfs> BTree<V> {
394413 // their sibling links remain zero until flush.
395414 let mut path = self . path_to_leaf_for_key ( start) . await ?;
396415 let mut keys_to_delete: Vec < Vec < u8 > > = Vec :: new ( ) ;
397- let mut seen_leaves = SeenPageIds :: new ( ) ;
416+ let mut seen_leaves = SeenPageIds :: new ( "leaf_siblings" ) ;
398417 ' outer: loop {
399418 let leaf_page_id = * path. last ( ) . expect ( "non-empty path" ) ;
400419 seen_leaves. insert ( leaf_page_id) ?;
@@ -416,9 +435,11 @@ impl<V: Vfs> BTree<V> {
416435 {
417436 path = parent_next;
418437 }
419- _ => {
420- return Err ( PagedbError :: corruption (
421- CorruptionDetail :: HeaderUnverifiable ,
438+ ( right_sibling, parent_next) => {
439+ return Err ( PagedbError :: leaf_sibling_mismatch (
440+ leaf_page_id,
441+ right_sibling,
442+ parent_next. and_then ( |path| path. last ( ) . copied ( ) ) ,
422443 ) ) ;
423444 }
424445 }
0 commit comments