Skip to main content

subetha_cxc/
shared_btree_map.rs

1//! `SharedBTreeMap` - cross-process MMF B-tree ordered map.
2//!
3//! A cross-process MMF ordered key/value map with a cache-friendly layout:
4//! each node packs up to `B` sorted keys (fanout `B + 1`), so a lookup
5//! touches ~`log_{B+1}(N)` nodes. The per-node binary search reads a
6//! contiguous key array (prefetcher-friendly) rather than chasing scattered
7//! single-cache-line nodes, which is what wins once the map far exceeds L3
8//! and lookups go to RAM. It is the substrate's ordered-map primitive.
9//!
10//! Minimum degree `T = 8` => up to `B = 2T - 1 = 15` keys per node, `2T = 16`
11//! children. Insert uses CLRS proactive top-down splitting (full children
12//! are split before descent), so it is single-pass and never overflows.
13//!
14//! Storage: self-contained MMF `[BTreeHeader | BTreeNode array]` with bump
15//! allocation. Concurrency model: single-writer for `insert` / `remove`
16//! (serialise externally); reads are consistent
17//! against a quiescent tree (build-then-query), which is what the cold
18//! benchmark exercises.
19
20use std::fs::{File, OpenOptions};
21use std::mem::size_of;
22use std::path::Path;
23use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
24
25use memmap2::{MmapMut, MmapOptions};
26
27/// Minimum degree.
28pub const T: usize = 8;
29/// Max keys per node.
30pub const B: usize = 2 * T - 1; // 15
31/// Sentinel "no node".
32pub const NIL: u32 = u32::MAX;
33
34pub const BTREE_MAGIC: u64 = 0x4254_5245_454D_4150; // "BTREEMAP"
35
36#[repr(C, align(64))]
37pub struct BTreeHeader {
38    pub magic: u64,
39    pub root: AtomicU32,
40    pub node_count: AtomicU32,
41    pub capacity: u64,
42    pub len: AtomicU64,
43    /// Head of the single-writer free list (NIL = empty); merged/removed
44    /// nodes are recycled here so deletes reclaim slots.
45    pub free_head: AtomicU32,
46    _pad0: u32,
47    /// Global seqlock. A writer makes it odd for the duration of a
48    /// structural mutation (insert/remove) and even after; readers retry
49    /// the whole search if it changes or is odd, so concurrent reads never
50    /// observe a torn tree. Single-writer, multi-reader.
51    pub version: AtomicU64,
52    _pad: [u8; 16],
53}
54
55const _: () = {
56    assert!(size_of::<BTreeHeader>() == 64);
57};
58
59#[repr(C)]
60pub struct BTreeNode<K: Copy + Ord + Default + 'static, V: Copy + Default + 'static> {
61    pub count: u16,
62    pub is_leaf: u8,
63    _pad: [u8; 5],
64    pub keys: [K; B],
65    pub children: [u32; B + 1],
66    pub values: [V; B],
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum BTreeError {
71    Full,
72    LayoutMismatch,
73    InvalidConfig,
74    IoError(std::io::ErrorKind),
75}
76
77impl From<std::io::Error> for BTreeError {
78    fn from(e: std::io::Error) -> Self {
79        Self::IoError(e.kind())
80    }
81}
82
83pub fn btree_file_size<K, V>(capacity: usize) -> usize
84where
85    K: Copy + Ord + Default + 'static,
86    V: Copy + Default + 'static,
87{
88    size_of::<BTreeHeader>() + capacity * size_of::<BTreeNode<K, V>>()
89}
90
91pub struct SharedBTreeMap<K: Copy + Ord + Default + 'static, V: Copy + Default + 'static> {
92    _file: File,
93    mmap: MmapMut,
94    raw_ptr: *mut u8,
95    capacity: usize,
96    _phantom: std::marker::PhantomData<(K, V)>,
97    header_sidecar: subetha_core::HandshakeHeader,
98    ring_sidecar: Box<subetha_core::ObservationRing>,
99}
100
101unsafe impl<K: Copy + Ord + Default + Send + 'static, V: Copy + Default + Send + 'static> Send
102    for SharedBTreeMap<K, V>
103{
104}
105unsafe impl<K: Copy + Ord + Default + Sync + 'static, V: Copy + Default + Sync + 'static> Sync
106    for SharedBTreeMap<K, V>
107{
108}
109
110impl<K: Copy + Ord + Default + Send + Sync + 'static, V: Copy + Default + Send + Sync + 'static>
111    subetha_sidecar::AdaptiveInstance for SharedBTreeMap<K, V>
112{
113    fn header(&self) -> &subetha_core::HandshakeHeader { &self.header_sidecar }
114    fn ring(&self) -> &subetha_core::ObservationRing { &self.ring_sidecar }
115    fn make_policy(&self) -> Box<dyn subetha_sidecar::Policy> {
116        Box::new(subetha_sidecar::NoMigrationPolicy)
117    }
118}
119
120impl<K: Copy + Ord + Default + 'static, V: Copy + Default + 'static> SharedBTreeMap<K, V> {
121    pub fn create(path: impl AsRef<Path>, capacity: usize) -> Result<Self, BTreeError> {
122        if capacity < 1 {
123            return Err(BTreeError::InvalidConfig);
124        }
125        let total = btree_file_size::<K, V>(capacity);
126        let file = OpenOptions::new()
127            .read(true).write(true).create(true).truncate(true)
128            .open(path.as_ref())?;
129        file.set_len(total as u64)?;
130        let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
131        let hdr = mmap.as_mut_ptr() as *mut BTreeHeader;
132        unsafe {
133            std::ptr::write_bytes(hdr as *mut u8, 0, size_of::<BTreeHeader>());
134            (*hdr).magic = BTREE_MAGIC;
135            (*hdr).root = AtomicU32::new(NIL);
136            (*hdr).node_count = AtomicU32::new(0);
137            (*hdr).capacity = capacity as u64;
138            (*hdr).len = AtomicU64::new(0);
139            (*hdr).free_head = AtomicU32::new(NIL);
140            (*hdr).version = AtomicU64::new(0);
141        }
142        let raw_ptr = mmap.as_mut_ptr();
143        Ok(Self {
144            _file: file, mmap, raw_ptr, capacity,
145            _phantom: std::marker::PhantomData,
146            header_sidecar: subetha_core::HandshakeHeader::new(),
147            ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
148        })
149    }
150
151    pub fn open(path: impl AsRef<Path>, expected_capacity: usize) -> Result<Self, BTreeError> {
152        let total = btree_file_size::<K, V>(expected_capacity);
153        let file = OpenOptions::new().read(true).write(true).open(path.as_ref())?;
154        if file.metadata()?.len() < total as u64 {
155            return Err(BTreeError::LayoutMismatch);
156        }
157        let mut mmap = unsafe { MmapOptions::new().len(total).map_mut(&file)? };
158        let hdr = unsafe { &*(mmap.as_ptr() as *const BTreeHeader) };
159        if hdr.magic != BTREE_MAGIC || hdr.capacity != expected_capacity as u64 {
160            return Err(BTreeError::LayoutMismatch);
161        }
162        let raw_ptr = mmap.as_mut_ptr();
163        Ok(Self {
164            _file: file, mmap, raw_ptr, capacity: expected_capacity,
165            _phantom: std::marker::PhantomData,
166            header_sidecar: subetha_core::HandshakeHeader::new(),
167            ring_sidecar: Box::new(subetha_core::ObservationRing::new()),
168        })
169    }
170
171    #[inline]
172    fn header(&self) -> &BTreeHeader {
173        unsafe { &*(self.raw_ptr as *const BTreeHeader) }
174    }
175
176    #[inline]
177    pub fn len(&self) -> usize {
178        self.header().len.load(Ordering::Acquire) as usize
179    }
180
181    #[inline]
182    pub fn is_empty(&self) -> bool {
183        self.len() == 0
184    }
185
186    #[inline]
187    pub fn capacity(&self) -> usize {
188        self.capacity
189    }
190
191    /// Number of nodes bump-allocated so far (resident-memory witness:
192    /// `node_count() * size_of::<BTreeNode>()`).
193    #[inline]
194    pub fn node_count(&self) -> usize {
195        self.header().node_count.load(Ordering::Acquire) as usize
196    }
197
198    #[inline]
199    fn node(&self, idx: u32) -> *mut BTreeNode<K, V> {
200        let off = size_of::<BTreeHeader>() + idx as usize * size_of::<BTreeNode<K, V>>();
201        unsafe { self.raw_ptr.add(off) as *mut BTreeNode<K, V> }
202    }
203
204    /// Allocate a node: recycle from the free list, else bump-allocate.
205    /// Single-writer, so the free list needs no ABA protection.
206    fn alloc_node(&self, is_leaf: bool) -> Result<u32, BTreeError> {
207        let h = self.header();
208        let free = h.free_head.load(Ordering::Acquire);
209        let idx = if free != NIL {
210            let next = unsafe { (*self.node(free)).children[0] };
211            h.free_head.store(next, Ordering::Release);
212            free
213        } else {
214            let idx = h.node_count.fetch_add(1, Ordering::AcqRel);
215            if idx as usize >= self.capacity {
216                h.node_count.fetch_sub(1, Ordering::AcqRel);
217                return Err(BTreeError::Full);
218            }
219            idx
220        };
221        let n = self.node(idx);
222        unsafe {
223            (*n).count = 0;
224            (*n).is_leaf = is_leaf as u8;
225            (*n).children = [NIL; B + 1];
226        }
227        Ok(idx)
228    }
229
230    /// Return a node to the free list (linked via `children[0]`).
231    fn free_node(&self, idx: u32) {
232        let h = self.header();
233        let head = h.free_head.load(Ordering::Acquire);
234        unsafe { (*self.node(idx)).children[0] = head; }
235        h.free_head.store(idx, Ordering::Release);
236    }
237
238    /// Enter / leave a structural mutation. The global version is odd while
239    /// a write is in flight; readers retry the whole search if they observe
240    /// an odd or changed version (seqlock). Single-writer.
241    #[inline]
242    fn begin_write(&self) {
243        self.header().version.fetch_add(1, Ordering::AcqRel);
244    }
245    #[inline]
246    fn end_write(&self) {
247        self.header().version.fetch_add(1, Ordering::Release);
248    }
249
250    /// Binary search `key` within node `idx`. Returns `(pos, found)`: when
251    /// found, `keys[pos] == key`; otherwise `pos` is the child slot / leaf
252    /// insertion position. `count` is clamped to `B` so a torn read under a
253    /// concurrent writer can never index past the fixed-size arrays.
254    #[inline]
255    fn search(&self, idx: u32, key: &K) -> (usize, bool) {
256        let n = self.node(idx);
257        let count = (unsafe { (*n).count } as usize).min(B);
258        let (mut lo, mut hi) = (0usize, count);
259        while lo < hi {
260            let mid = (lo + hi) / 2;
261            let mk = unsafe { (*n).keys[mid] };
262            match mk.cmp(key) {
263                std::cmp::Ordering::Less => lo = mid + 1,
264                std::cmp::Ordering::Greater => hi = mid,
265                std::cmp::Ordering::Equal => return (mid, true),
266            }
267        }
268        (lo, false)
269    }
270
271    /// Look up `key`. Lock-free read against a quiescent tree.
272    pub fn get(&self, key: &K) -> Option<V> {
273        let r = self.get_inner(key);
274        self.ring_sidecar.push_op(
275            crate::sidecar_ops::ordered::OP_GET,
276            if r.is_none() { 2 } else { 0 },
277        );
278        r
279    }
280
281    fn get_inner(&self, key: &K) -> Option<V> {
282        let h = self.header();
283        let cap = self.capacity as u32;
284        loop {
285            let v1 = h.version.load(Ordering::Acquire);
286            if v1 & 1 != 0 {
287                std::hint::spin_loop();
288                continue; // a writer is mid-mutation
289            }
290            // Descend. Every node index is bounds-guarded so a torn read
291            // (a concurrent split moving keys) cannot deref out of range;
292            // `search` clamps `count`. If anything looks inconsistent we
293            // simply finish and let the version re-check force a retry.
294            let mut idx = h.root.load(Ordering::Acquire);
295            let mut result: Option<V> = None;
296            let mut torn = false;
297            while idx != NIL {
298                if idx >= cap {
299                    torn = true;
300                    break;
301                }
302                let (pos, found) = self.search(idx, key);
303                let n = self.node(idx);
304                if found {
305                    result = Some(unsafe { (*n).values[pos] });
306                    break;
307                }
308                if unsafe { (*n).is_leaf } != 0 {
309                    break;
310                }
311                idx = unsafe { (*n).children[pos] };
312            }
313            let v2 = h.version.load(Ordering::Acquire);
314            if v1 == v2 && !torn {
315                return result;
316            }
317            std::hint::spin_loop();
318        }
319    }
320
321    /// True if `key` is present.
322    pub fn contains_key(&self, key: &K) -> bool {
323        self.get_inner(key).is_some()
324    }
325
326    /// Insert / update. Single-writer (serialise externally). Returns the
327    /// previous value if `key` was present.
328    pub fn insert(&self, key: K, value: V) -> Result<Option<V>, BTreeError> {
329        self.begin_write();
330        let r = self.insert_inner(key, value);
331        self.end_write();
332        self.ring_sidecar.push_op(
333            crate::sidecar_ops::ordered::OP_INSERT,
334            if r.is_err() { 1 } else { 0 },
335        );
336        r
337    }
338
339    fn insert_inner(&self, key: K, value: V) -> Result<Option<V>, BTreeError> {
340        let root = self.header().root.load(Ordering::Acquire);
341        if root == NIL {
342            let r = self.alloc_node(true)?;
343            let n = self.node(r);
344            unsafe {
345                (*n).keys[0] = key;
346                (*n).values[0] = value;
347                (*n).count = 1;
348            }
349            self.header().root.store(r, Ordering::Release);
350            self.header().len.fetch_add(1, Ordering::AcqRel);
351            return Ok(None);
352        }
353        // Grow height if the root is full.
354        let root = if unsafe { (*self.node(root)).count as usize } == B {
355            let new_root = self.alloc_node(false)?;
356            unsafe {
357                (*self.node(new_root)).children[0] = root;
358                (*self.node(new_root)).count = 0;
359            }
360            self.split_child(new_root, 0)?;
361            self.header().root.store(new_root, Ordering::Release);
362            new_root
363        } else {
364            root
365        };
366        self.insert_nonfull(root, key, value)
367    }
368
369    /// Insert into a guaranteed-non-full subtree rooted at `idx`.
370    fn insert_nonfull(&self, mut idx: u32, key: K, value: V) -> Result<Option<V>, BTreeError> {
371        loop {
372            let (pos, found) = self.search(idx, &key);
373            let n = self.node(idx);
374            if found {
375                let old = unsafe { (*n).values[pos] };
376                unsafe { (*n).values[pos] = value; }
377                return Ok(Some(old));
378            }
379            if unsafe { (*n).is_leaf } != 0 {
380                let count = unsafe { (*n).count as usize };
381                unsafe {
382                    let mut j = count;
383                    while j > pos {
384                        (*n).keys[j] = (*n).keys[j - 1];
385                        (*n).values[j] = (*n).values[j - 1];
386                        j -= 1;
387                    }
388                    (*n).keys[pos] = key;
389                    (*n).values[pos] = value;
390                    (*n).count = (count + 1) as u16;
391                }
392                self.header().len.fetch_add(1, Ordering::AcqRel);
393                return Ok(None);
394            }
395            let child = unsafe { (*n).children[pos] };
396            if unsafe { (*self.node(child)).count as usize } == B {
397                self.split_child(idx, pos)?;
398                // After split, the promoted median sits at keys[pos].
399                let n = self.node(idx);
400                let med = unsafe { (*n).keys[pos] };
401                match key.cmp(&med) {
402                    std::cmp::Ordering::Equal => {
403                        let old = unsafe { (*n).values[pos] };
404                        unsafe { (*n).values[pos] = value; }
405                        return Ok(Some(old));
406                    }
407                    std::cmp::Ordering::Greater => idx = unsafe { (*n).children[pos + 1] },
408                    std::cmp::Ordering::Less => idx = unsafe { (*n).children[pos] },
409                }
410            } else {
411                idx = child;
412            }
413        }
414    }
415
416    /// Split the full child at `parent.children[i]` into two, promoting the
417    /// median (key+value) into `parent` at position `i`.
418    fn split_child(&self, parent: u32, i: usize) -> Result<(), BTreeError> {
419        let full = unsafe { (*self.node(parent)).children[i] };
420        let is_leaf = unsafe { (*self.node(full)).is_leaf };
421        let right = self.alloc_node(is_leaf != 0)?;
422
423        let fp = self.node(full);
424        let rp = self.node(right);
425        unsafe {
426            // right gets the upper T-1 keys/values.
427            for j in 0..(T - 1) {
428                (*rp).keys[j] = (*fp).keys[T + j];
429                (*rp).values[j] = (*fp).values[T + j];
430            }
431            if is_leaf == 0 {
432                for j in 0..T {
433                    (*rp).children[j] = (*fp).children[T + j];
434                }
435            }
436            (*rp).count = (T - 1) as u16;
437            (*rp).is_leaf = is_leaf;
438        }
439        let median_key = unsafe { (*fp).keys[T - 1] };
440        let median_value = unsafe { (*fp).values[T - 1] };
441        // Left keeps the lower T-1 keys. Publish the new count last so a
442        // reader never sees the promoted/duplicated entries on the left.
443        unsafe { (*fp).count = (T - 1) as u16; }
444
445        let pp = self.node(parent);
446        unsafe {
447            let pc = (*pp).count as usize;
448            let mut j = pc;
449            while j > i {
450                (*pp).children[j + 1] = (*pp).children[j];
451                j -= 1;
452            }
453            (*pp).children[i + 1] = right;
454            let mut j = pc;
455            while j > i {
456                (*pp).keys[j] = (*pp).keys[j - 1];
457                (*pp).values[j] = (*pp).values[j - 1];
458                j -= 1;
459            }
460            (*pp).keys[i] = median_key;
461            (*pp).values[i] = median_value;
462            (*pp).count = (pc + 1) as u16;
463        }
464        Ok(())
465    }
466
467    /// Remove `key`, returning its previous value if present. Single-writer
468    /// (serialise externally, as with insert).
469    pub fn remove(&self, key: &K) -> Result<Option<V>, BTreeError> {
470        self.begin_write();
471        let r = self.remove_inner(key);
472        self.end_write();
473        self.ring_sidecar.push_op(
474            crate::sidecar_ops::ordered::OP_REMOVE,
475            if r.is_none() { 2 } else { 0 },
476        );
477        Ok(r)
478    }
479
480    fn remove_inner(&self, key: &K) -> Option<V> {
481        let root = self.header().root.load(Ordering::Acquire);
482        if root == NIL {
483            return None;
484        }
485        let removed = self.delete_from(root, key);
486        // Shrink height if the root emptied.
487        let rn = self.node(root);
488        if unsafe { (*rn).count } == 0 {
489            if unsafe { (*rn).is_leaf } != 0 {
490                self.header().root.store(NIL, Ordering::Release);
491            } else {
492                let new_root = unsafe { (*rn).children[0] };
493                self.header().root.store(new_root, Ordering::Release);
494            }
495            self.free_node(root);
496        }
497        if removed.is_some() {
498            self.header().len.fetch_sub(1, Ordering::AcqRel);
499        }
500        removed
501    }
502
503    /// Delete `key` from the subtree at `idx` (guaranteed >= T keys, or the
504    /// root). CLRS deletion: from a leaf directly; from an internal node by
505    /// replacing with the in-order predecessor/successor, or merging.
506    fn delete_from(&self, idx: u32, key: &K) -> Option<V> {
507        let (pos, found) = self.search(idx, key);
508        let n = self.node(idx);
509        let is_leaf = unsafe { (*n).is_leaf } != 0;
510        if found {
511            let old = unsafe { (*n).values[pos] };
512            if is_leaf {
513                let count = unsafe { (*n).count as usize };
514                unsafe {
515                    for j in pos..count - 1 {
516                        (*n).keys[j] = (*n).keys[j + 1];
517                        (*n).values[j] = (*n).values[j + 1];
518                    }
519                    (*n).count = (count - 1) as u16;
520                }
521            } else {
522                let left = unsafe { (*n).children[pos] };
523                let right = unsafe { (*n).children[pos + 1] };
524                if unsafe { (*self.node(left)).count as usize } >= T {
525                    let (pk, pv) = self.max_pair(left);
526                    unsafe {
527                        (*n).keys[pos] = pk;
528                        (*n).values[pos] = pv;
529                    }
530                    self.delete_from(left, &pk);
531                } else if unsafe { (*self.node(right)).count as usize } >= T {
532                    let (sk, sv) = self.min_pair(right);
533                    unsafe {
534                        (*n).keys[pos] = sk;
535                        (*n).values[pos] = sv;
536                    }
537                    self.delete_from(right, &sk);
538                } else {
539                    self.merge_at(idx, pos);
540                    self.delete_from(left, key);
541                }
542            }
543            return Some(old);
544        }
545        if is_leaf {
546            return None;
547        }
548        let child = self.ensure_min_degree(idx, pos);
549        self.delete_from(child, key)
550    }
551
552    /// Ensure `parent.children[i]` has >= T keys before descending, by
553    /// borrowing from a sibling or merging. Returns the index to descend.
554    fn ensure_min_degree(&self, parent: u32, i: usize) -> u32 {
555        let p = self.node(parent);
556        let child = unsafe { (*p).children[i] };
557        if unsafe { (*self.node(child)).count as usize } >= T {
558            return child;
559        }
560        let pcount = unsafe { (*p).count as usize };
561        if i > 0 {
562            let left = unsafe { (*p).children[i - 1] };
563            if unsafe { (*self.node(left)).count as usize } >= T {
564                self.borrow_from_left(parent, i);
565                return child;
566            }
567        }
568        if i < pcount {
569            let right = unsafe { (*p).children[i + 1] };
570            if unsafe { (*self.node(right)).count as usize } >= T {
571                self.borrow_from_right(parent, i);
572                return child;
573            }
574        }
575        if i < pcount {
576            self.merge_at(parent, i);
577            unsafe { (*self.node(parent)).children[i] }
578        } else {
579            self.merge_at(parent, i - 1);
580            unsafe { (*self.node(parent)).children[i - 1] }
581        }
582    }
583
584    fn borrow_from_left(&self, parent: u32, i: usize) {
585        let p = self.node(parent);
586        let child = unsafe { (*p).children[i] };
587        let left = unsafe { (*p).children[i - 1] };
588        let c = self.node(child);
589        let l = self.node(left);
590        unsafe {
591            let cc = (*c).count as usize;
592            let internal = (*c).is_leaf == 0;
593            let mut j = cc;
594            while j > 0 {
595                (*c).keys[j] = (*c).keys[j - 1];
596                (*c).values[j] = (*c).values[j - 1];
597                j -= 1;
598            }
599            if internal {
600                let mut j = cc + 1;
601                while j > 0 {
602                    (*c).children[j] = (*c).children[j - 1];
603                    j -= 1;
604                }
605            }
606            (*c).keys[0] = (*p).keys[i - 1];
607            (*c).values[0] = (*p).values[i - 1];
608            let lc = (*l).count as usize;
609            if internal {
610                (*c).children[0] = (*l).children[lc];
611            }
612            (*p).keys[i - 1] = (*l).keys[lc - 1];
613            (*p).values[i - 1] = (*l).values[lc - 1];
614            (*l).count = (lc - 1) as u16;
615            (*c).count = (cc + 1) as u16;
616        }
617    }
618
619    fn borrow_from_right(&self, parent: u32, i: usize) {
620        let p = self.node(parent);
621        let child = unsafe { (*p).children[i] };
622        let right = unsafe { (*p).children[i + 1] };
623        let c = self.node(child);
624        let r = self.node(right);
625        unsafe {
626            let cc = (*c).count as usize;
627            let internal = (*c).is_leaf == 0;
628            (*c).keys[cc] = (*p).keys[i];
629            (*c).values[cc] = (*p).values[i];
630            if internal {
631                (*c).children[cc + 1] = (*r).children[0];
632            }
633            (*p).keys[i] = (*r).keys[0];
634            (*p).values[i] = (*r).values[0];
635            let rc = (*r).count as usize;
636            for j in 0..rc - 1 {
637                (*r).keys[j] = (*r).keys[j + 1];
638                (*r).values[j] = (*r).values[j + 1];
639            }
640            if internal {
641                for j in 0..rc {
642                    (*r).children[j] = (*r).children[j + 1];
643                }
644            }
645            (*r).count = (rc - 1) as u16;
646            (*c).count = (cc + 1) as u16;
647        }
648    }
649
650    /// Merge `children[i]` + separator `keys[i]` + `children[i+1]` into
651    /// `children[i]`, freeing the right node and dropping the separator.
652    fn merge_at(&self, parent: u32, i: usize) {
653        let p = self.node(parent);
654        let left = unsafe { (*p).children[i] };
655        let right = unsafe { (*p).children[i + 1] };
656        let l = self.node(left);
657        let r = self.node(right);
658        unsafe {
659            let lc = (*l).count as usize;
660            let internal = (*l).is_leaf == 0;
661            (*l).keys[lc] = (*p).keys[i];
662            (*l).values[lc] = (*p).values[i];
663            let rc = (*r).count as usize;
664            for j in 0..rc {
665                (*l).keys[lc + 1 + j] = (*r).keys[j];
666                (*l).values[lc + 1 + j] = (*r).values[j];
667            }
668            if internal {
669                for j in 0..=rc {
670                    (*l).children[lc + 1 + j] = (*r).children[j];
671                }
672            }
673            (*l).count = (lc + 1 + rc) as u16;
674            let pc = (*p).count as usize;
675            for j in i..pc - 1 {
676                (*p).keys[j] = (*p).keys[j + 1];
677                (*p).values[j] = (*p).values[j + 1];
678            }
679            for j in i + 1..pc {
680                (*p).children[j] = (*p).children[j + 1];
681            }
682            (*p).count = (pc - 1) as u16;
683        }
684        self.free_node(right);
685    }
686
687    fn max_pair(&self, mut idx: u32) -> (K, V) {
688        loop {
689            let n = self.node(idx);
690            let count = unsafe { (*n).count as usize };
691            if unsafe { (*n).is_leaf } != 0 {
692                return unsafe { ((*n).keys[count - 1], (*n).values[count - 1]) };
693            }
694            idx = unsafe { (*n).children[count] };
695        }
696    }
697
698    fn min_pair(&self, mut idx: u32) -> (K, V) {
699        loop {
700            let n = self.node(idx);
701            if unsafe { (*n).is_leaf } != 0 {
702                return unsafe { ((*n).keys[0], (*n).values[0]) };
703            }
704            idx = unsafe { (*n).children[0] };
705        }
706    }
707
708    /// Smallest (key, value) in the map, or `None` if empty.
709    pub fn first(&self) -> Option<(K, V)> {
710        let root = self.header().root.load(Ordering::Acquire);
711        let r = if root == NIL { None } else { Some(self.min_pair(root)) };
712        self.ring_sidecar.push_op(
713            crate::sidecar_ops::ordered::OP_GET,
714            if r.is_none() { 2 } else { 0 },
715        );
716        r
717    }
718
719    /// Collect all (K, V) in ascending key order (validation / iteration).
720    pub fn iter_ascending(&self) -> Vec<(K, V)> {
721        let mut out = Vec::with_capacity(self.len());
722        let root = self.header().root.load(Ordering::Acquire);
723        if root != NIL {
724            self.walk(root, &mut out);
725        }
726        out
727    }
728
729    fn walk(&self, idx: u32, out: &mut Vec<(K, V)>) {
730        let n = self.node(idx);
731        let count = unsafe { (*n).count as usize };
732        let is_leaf = unsafe { (*n).is_leaf } != 0;
733        for i in 0..count {
734            if !is_leaf {
735                let c = unsafe { (*n).children[i] };
736                self.walk(c, out);
737            }
738            out.push(unsafe { ((*n).keys[i], (*n).values[i]) });
739        }
740        if !is_leaf {
741            let c = unsafe { (*n).children[count] };
742            self.walk(c, out);
743        }
744    }
745
746    pub fn flush(&self) -> Result<(), BTreeError> {
747        self.mmap.flush()?;
748        Ok(())
749    }
750}
751
752#[cfg(test)]
753mod tests {
754    use super::*;
755
756    fn tmp(name: &str) -> std::path::PathBuf {
757        let mut p = std::env::temp_dir();
758        p.push(format!("btree_{name}_{}.bin", std::process::id()));
759        p
760    }
761
762    #[test]
763    fn insert_get_round_trip() {
764        let p = tmp("rt");
765        let m: SharedBTreeMap<u64, u64> = SharedBTreeMap::create(&p, 64).unwrap();
766        for i in 0..100u64 {
767            assert_eq!(m.insert(i, i * 10).unwrap(), None);
768        }
769        for i in 0..100u64 {
770            assert_eq!(m.get(&i), Some(i * 10));
771        }
772        assert_eq!(m.get(&1000), None);
773        assert_eq!(m.len(), 100);
774        std::fs::remove_file(&p).ok();
775    }
776
777    #[test]
778    fn duplicate_insert_updates_and_returns_previous() {
779        let p = tmp("dup");
780        let m: SharedBTreeMap<u64, u64> = SharedBTreeMap::create(&p, 64).unwrap();
781        assert_eq!(m.insert(5, 50).unwrap(), None);
782        assert_eq!(m.insert(5, 55).unwrap(), Some(50));
783        assert_eq!(m.get(&5), Some(55));
784        assert_eq!(m.len(), 1);
785        std::fs::remove_file(&p).ok();
786    }
787
788    #[test]
789    fn many_inserts_stay_sorted_and_findable() {
790        let p = tmp("many");
791        let n = 5000u64;
792        let m: SharedBTreeMap<u64, u64> = SharedBTreeMap::create(&p, 4096).unwrap();
793        // Pseudo-random insertion order.
794        let mut x = 0x1234_5678u64;
795        let mut inserted = Vec::new();
796        for _ in 0..n {
797            x ^= x << 13; x ^= x >> 7; x ^= x << 17;
798            let k = x % 1_000_000;
799            if m.insert(k, k.wrapping_mul(3)).unwrap().is_none() {
800                inserted.push(k);
801            }
802        }
803        for &k in &inserted {
804            assert_eq!(m.get(&k), Some(k.wrapping_mul(3)), "missing {k}");
805        }
806        // Ascending order holds across all splits.
807        let asc = m.iter_ascending();
808        for w in asc.windows(2) {
809            assert!(w[0].0 < w[1].0, "order broken: {} >= {}", w[0].0, w[1].0);
810        }
811        assert_eq!(asc.len(), inserted.len());
812        std::fs::remove_file(&p).ok();
813    }
814
815    #[test]
816    fn cross_handle_visibility() {
817        let p = tmp("xhandle");
818        let a: SharedBTreeMap<u64, u64> = SharedBTreeMap::create(&p, 64).unwrap();
819        for i in 0..50u64 { a.insert(i, i).unwrap(); }
820        let b: SharedBTreeMap<u64, u64> = SharedBTreeMap::open(&p, 64).unwrap();
821        for i in 0..50u64 { assert_eq!(b.get(&i), Some(i)); }
822        std::fs::remove_file(&p).ok();
823    }
824
825    #[test]
826    fn random_ops_match_std_btreemap() {
827        use std::collections::BTreeMap;
828        let p = tmp("oracle");
829        let m: SharedBTreeMap<u64, u64> = SharedBTreeMap::create(&p, 16384).unwrap();
830        let mut oracle: BTreeMap<u64, u64> = BTreeMap::new();
831        let mut x = 0xdead_beef_1234_5678u64;
832        let mut rng = || {
833            x ^= x << 13; x ^= x >> 7; x ^= x << 17; x
834        };
835        for _ in 0..80_000 {
836            let k = rng() % 3000;
837            match rng() % 3 {
838                0 | 1 => {
839                    let v = rng();
840                    assert_eq!(m.insert(k, v).unwrap(), oracle.insert(k, v), "insert {k}");
841                }
842                _ => {
843                    assert_eq!(m.remove(&k).unwrap(), oracle.remove(&k), "remove {k}");
844                }
845            }
846            assert_eq!(m.len(), oracle.len(), "len after op on {k}");
847        }
848        for (k, v) in &oracle {
849            assert_eq!(m.get(k), Some(*v), "final get {k}");
850        }
851        let asc = m.iter_ascending();
852        let oref: Vec<(u64, u64)> = oracle.iter().map(|(k, v)| (*k, *v)).collect();
853        assert_eq!(asc, oref, "iteration order / contents mismatch");
854        std::fs::remove_file(&p).ok();
855    }
856
857    #[test]
858    fn first_returns_smallest() {
859        let p = tmp("first");
860        let m: SharedBTreeMap<u64, u64> = SharedBTreeMap::create(&p, 1024).unwrap();
861        assert_eq!(m.first(), None);
862        for i in (0..500u64).rev() {
863            m.insert(i, i * 7).unwrap();
864        }
865        assert_eq!(m.first(), Some((0, 0)));
866        m.remove(&0).unwrap();
867        assert_eq!(m.first(), Some((1, 7)));
868        std::fs::remove_file(&p).ok();
869    }
870
871    #[test]
872    fn concurrent_readers_during_inserts() {
873        use std::sync::Arc;
874        use std::sync::atomic::AtomicBool;
875        let p = tmp("concurrent");
876        let m = Arc::new(SharedBTreeMap::<u64, u64>::create(&p, 16384).unwrap());
877        for i in 0..1000u64 {
878            m.insert(i, i.wrapping_mul(2)).unwrap();
879        }
880        let stop = Arc::new(AtomicBool::new(false));
881        let readers: Vec<_> = (0..4)
882            .map(|_| {
883                let m = m.clone();
884                let stop = stop.clone();
885                std::thread::spawn(move || {
886                    while !stop.load(Ordering::Relaxed) {
887                        for i in 0..1000u64 {
888                            // Keys 0..1000 are never removed; a reader must
889                            // see the exact value or (transiently) retry to
890                            // it - never a torn / garbage value.
891                            if let Some(v) = m.get(&i) {
892                                assert_eq!(v, i.wrapping_mul(2), "torn read at {i}");
893                            }
894                        }
895                    }
896                })
897            })
898            .collect();
899        for i in 1000..6000u64 {
900            m.insert(i, i.wrapping_mul(2)).unwrap();
901        }
902        stop.store(true, Ordering::Relaxed);
903        for r in readers {
904            r.join().unwrap();
905        }
906        std::fs::remove_file(&p).ok();
907    }
908}