1use std::cell::UnsafeCell;
5use std::collections::{HashMap, VecDeque};
6use std::mem::ManuallyDrop;
7use std::ops::{Deref, DerefMut};
8use std::panic;
9use std::path::Path;
10use std::path::PathBuf;
11use std::sync::Arc;
12
13#[cfg(not(all(feature = "shuttle", test)))]
14use rand::Rng;
15#[cfg(all(feature = "shuttle", test))]
16use shuttle::rand::Rng;
17
18#[cfg(unix)]
19use std::os::unix::fs::FileExt;
20#[cfg(windows)]
21use std::os::windows::fs::FileExt;
22
23#[cfg(any(feature = "metrics-rt-debug-all", feature = "metrics-rt-debug-timer"))]
24use thread_local::ThreadLocal;
25
26use crate::{
27 circular_buffer::CircularBuffer,
28 error::ConfigError,
29 fs::VfsImpl,
30 mini_page_op::LeafOperations,
31 nodes::{leaf_node::MiniPageNextLevel, LeafNode, INVALID_DISK_OFFSET},
32 nodes::{InnerNode, InnerNodeBuilder, PageID, DISK_PAGE_SIZE, INNER_NODE_SIZE},
33 storage::{make_vfs, LeafStorage, PageLocation, PageTable},
34 sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering},
35 sync::RwLock,
36 utils::{atomic_wait, get_rng, inner_lock::ReadGuard, BfsVisitor, NodeInfo},
37 wal::{LogEntry, LogEntryImpl, WriteAheadLog},
38 BfTree, Config, StorageBackend, WalConfig, WalReader,
39};
40#[allow(unused_imports)]
42use crate::sync::thread;
43
44const BF_TREE_MAGIC_BEGIN: &[u8; 16] = b"BF-TREE-V0-BEGIN";
45const BF_TREE_MAGIC_END: &[u8; 14] = b"BF-TREE-V0-END";
46const META_DATA_PAGE_OFFSET: usize = 0;
47
48const INVALID_SNAPSHOT_THREAD_ID: usize = usize::MAX; const NULL_PAGE_LOCATION_OFFSET: usize = usize::MAX; const INVALID_SNAPSHOT_STATE: u64 = u64::MAX; pub const INVALID_SNAPSHOT_VERSION: u64 = u64::MAX >> 1; const DEFAULT_MAX_SNAPSHOT_THREAD_NUM: usize = 64; const SNAPSHOT_STATE_PHASE_ID_SHIFT: usize = 61; const SNAPSHOT_STATE_PHASE_NUM: u64 = 4; const SNAPSHOT_STATE_PHASE_ID_MASK: u64 = 0b111 << SNAPSHOT_STATE_PHASE_ID_SHIFT; const SNAPSHOT_STATE_VERSION_MASK: u64 = (1 << SNAPSHOT_STATE_PHASE_ID_SHIFT) - 1; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum PhaseId {
61 Rest,
62 Prepare,
63 InProgress,
64 Sweep,
65}
66
67impl PhaseId {
68 fn from_raw(value: u64) -> Self {
69 match value {
70 0 => PhaseId::Rest,
71 1 => PhaseId::Prepare,
72 2 => PhaseId::InProgress,
73 3 => PhaseId::Sweep,
74 _ => panic!("Invalid phase id: {}", value),
75 }
76 }
77
78 fn as_raw(self) -> u64 {
79 match self {
80 PhaseId::Rest => 0,
81 PhaseId::Prepare => 1,
82 PhaseId::InProgress => 2,
83 PhaseId::Sweep => 3,
84 }
85 }
86}
87
88pub struct CPRSnapShotMgr {
98 global_state: AtomicU64,
102 thread_slots: [AtomicBool; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
104 thread_local_states: [AtomicU64; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
106 thread_local_inner_mappings:
109 UnsafeCell<[Vec<(*const InnerNode, usize)>; DEFAULT_MAX_SNAPSHOT_THREAD_NUM]>,
110 thread_local_base_mappings: UnsafeCell<[Vec<(PageID, usize)>; DEFAULT_MAX_SNAPSHOT_THREAD_NUM]>,
111 thread_local_mini_mappings: UnsafeCell<[Vec<(PageID, usize)>; DEFAULT_MAX_SNAPSHOT_THREAD_NUM]>,
112 thread_local_mini_size_mappings:
113 UnsafeCell<[Vec<(PageID, usize)>; DEFAULT_MAX_SNAPSHOT_THREAD_NUM]>,
114 root_id: AtomicU64, pause_snapshot: AtomicBool,
116 vfs: RwLock<Arc<dyn VfsImpl>>,
120 snapshot_in_progress: AtomicBool,
122 phase_waiter: AtomicU32,
125}
126
127unsafe impl Sync for CPRSnapShotMgr {}
128
129unsafe impl Send for CPRSnapShotMgr {}
130
131pub struct CPRSnapshotGuard {
133 snapshot_mgr: Option<Arc<CPRSnapShotMgr>>,
134 thread_slot_id: usize,
135 snapshot_version: u64,
136 phase_id: PhaseId,
137}
138
139impl CPRSnapshotGuard {
140 pub fn new(snapshot_mgr: Option<Arc<CPRSnapShotMgr>>) -> Result<Self, ()> {
141 match snapshot_mgr {
142 None => Ok(Self {
143 snapshot_mgr: None,
144 thread_slot_id: INVALID_SNAPSHOT_THREAD_ID,
145 snapshot_version: INVALID_SNAPSHOT_VERSION,
146 phase_id: PhaseId::Rest,
147 }),
148 Some(ref mgr) => {
149 let (thread_slot_id, snapshot_version, phase_id) = mgr.reserve_thread_slot()?;
150
151 Ok(Self {
152 snapshot_mgr: Some(mgr.clone()),
153 thread_slot_id,
154 snapshot_version,
155 phase_id,
156 })
157 }
158 }
159 }
160
161 pub fn snapshot_version(&self) -> u64 {
163 self.snapshot_version
164 }
165
166 pub fn get_local_phase_id(&self) -> PhaseId {
168 self.phase_id
169 }
170
171 pub fn is_protected(&self) -> bool {
173 self.thread_slot_id != INVALID_SNAPSHOT_THREAD_ID
174 }
175
176 pub fn snapshot_base_page(&self, id: PageID, ptr: &[u8], size: usize) {
177 self.snapshot_mgr
178 .as_ref()
179 .unwrap()
180 .snapshot_base_page(id, ptr, size, self.thread_slot_id);
181 }
182
183 pub fn snapshot_mini_page(&self, id: PageID, ptr: &[u8], size: usize) {
184 self.snapshot_mgr
185 .as_ref()
186 .unwrap()
187 .snapshot_mini_page(id, ptr, size, self.thread_slot_id);
188 }
189
190 pub fn snapshot_inner_node(&self, ptr: *const InnerNode) {
191 self.snapshot_mgr
192 .as_ref()
193 .unwrap()
194 .snapshot_inner_node(ptr, self.thread_slot_id);
195 }
196
197 pub fn snapshot_root_page(&self, root_id: PageID) {
198 self.snapshot_mgr
199 .as_ref()
200 .unwrap()
201 .snapshot_root_page(root_id);
202 }
203}
204
205impl Drop for CPRSnapshotGuard {
206 fn drop(&mut self) {
207 if let Some(ref mgr) = self.snapshot_mgr {
208 mgr.release_thread_slot(self.thread_slot_id);
209 }
210 }
211}
212
213impl CPRSnapShotMgr {
214 pub fn are_all_threads_in_next_version(&self) -> bool {
215 if !self.snapshot_in_progress.load(Ordering::Acquire) {
216 false
217 } else {
218 let global_phase_id = self.get_global_phase_id();
219 global_phase_id == PhaseId::Sweep || global_phase_id == PhaseId::Rest
220 }
221 }
222
223 pub fn new(version: u64) -> Self {
225 let vfs: Arc<dyn VfsImpl> = make_vfs(&StorageBackend::Memory, ":memory:");
226 Self {
227 global_state: AtomicU64::new(Self::new_snapshot_state(0, version)), thread_slots: [const { AtomicBool::new(false) }; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
229 thread_local_states: [const { AtomicU64::new(INVALID_SNAPSHOT_STATE) };
230 DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
231 thread_local_inner_mappings: UnsafeCell::new(
232 [const { Vec::new() }; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
233 ),
234 thread_local_base_mappings: UnsafeCell::new(
235 [const { Vec::new() }; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
236 ),
237 thread_local_mini_mappings: UnsafeCell::new(
238 [const { Vec::new() }; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
239 ),
240 thread_local_mini_size_mappings: UnsafeCell::new(
241 [const { Vec::new() }; DEFAULT_MAX_SNAPSHOT_THREAD_NUM],
242 ),
243 root_id: AtomicU64::new(0),
244 pause_snapshot: AtomicBool::new(false),
245 vfs: RwLock::new(vfs),
246 snapshot_in_progress: AtomicBool::new(false),
247 phase_waiter: AtomicU32::new(0),
248 }
249 }
250
251 fn reset(&self) {
253 let local_inner_mappings = unsafe { &mut *self.thread_local_inner_mappings.get() };
254 let local_mini_mappings = unsafe { &mut *self.thread_local_mini_mappings.get() };
255 let local_base_mappings = unsafe { &mut *self.thread_local_base_mappings.get() };
256 let local_mini_size_mappings = unsafe { &mut *self.thread_local_mini_size_mappings.get() };
257 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
259 local_inner_mappings[thread_slot_id] = Vec::new();
260 local_mini_mappings[thread_slot_id] = Vec::new();
261 local_base_mappings[thread_slot_id] = Vec::new();
262 local_mini_size_mappings[thread_slot_id] = Vec::new();
263 }
264
265 self.root_id.store(0, Ordering::Release);
266 }
267
268 pub fn new_snapshot_state(phase_id: u64, version: u64) -> u64 {
269 assert!(
270 phase_id < SNAPSHOT_STATE_PHASE_NUM,
271 "Phase id must be less than {}",
272 SNAPSHOT_STATE_PHASE_NUM
273 );
274 assert!(
275 version < (1 << SNAPSHOT_STATE_PHASE_ID_SHIFT),
276 "Version must be less than 2^61"
277 );
278 (phase_id << SNAPSHOT_STATE_PHASE_ID_SHIFT) | version
279 }
280
281 fn get_global_version(&self) -> u64 {
282 self.global_state.load(Ordering::Acquire) & SNAPSHOT_STATE_VERSION_MASK
283 }
284
285 fn get_global_phase_id(&self) -> PhaseId {
286 PhaseId::from_raw(
287 (self.global_state.load(Ordering::Acquire) & SNAPSHOT_STATE_PHASE_ID_MASK)
288 >> SNAPSHOT_STATE_PHASE_ID_SHIFT,
289 )
290 }
291
292 fn get_local_state(&self, thread_slot_id: &usize) -> u64 {
294 self.thread_local_states[*thread_slot_id].load(Ordering::Acquire)
295 }
296
297 fn set_local_state(&self, thread_slot_id: &usize, state: u64) {
298 let current_state = self.get_local_state(thread_slot_id);
300 if current_state == state {
301 return;
302 }
303
304 self.thread_local_states[*thread_slot_id].store(state, Ordering::Release);
305 }
306
307 fn advance_global_state(&self) -> u64 {
309 let phase_id = self.get_global_phase_id();
310 let version = self.get_global_version();
311
312 let new_state = match phase_id {
313 PhaseId::Rest => {
314 Self::new_snapshot_state(PhaseId::Prepare.as_raw(), version)
316 }
317 PhaseId::Prepare => {
318 Self::new_snapshot_state(PhaseId::InProgress.as_raw(), version + 1)
320 }
321 PhaseId::InProgress => {
322 Self::new_snapshot_state(PhaseId::Sweep.as_raw(), version)
324 }
325 PhaseId::Sweep => {
326 Self::new_snapshot_state(PhaseId::Rest.as_raw(), version)
328 }
329 };
330
331 self.global_state.store(new_state, Ordering::Release);
333
334 new_state
335 }
336
337 fn check_if_phase_completed(&self, target_state: u64) -> bool {
340 crate::sync::atomic::fence(Ordering::SeqCst);
345
346 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
348 let local_state = self.thread_local_states[thread_slot_id].load(Ordering::Acquire);
349 if local_state != INVALID_SNAPSHOT_STATE && local_state != target_state {
350 return false;
351 }
352 }
353 true
354 }
355
356 fn wait_for_phase_completion(&self, target_state: u64) {
359 loop {
360 if self.check_if_phase_completed(target_state) {
362 return;
363 }
364
365 let waiter_val = self.phase_waiter.load(Ordering::Acquire);
367
368 if self.check_if_phase_completed(target_state) {
370 return;
371 }
372
373 atomic_wait::wait(&self.phase_waiter, waiter_val);
375
376 #[cfg(target_os = "macos")]
378 thread::sleep(std::time::Duration::from_millis(1));
379 }
380 }
381
382 fn wait_for_all_slots_released(&self) {
385 loop {
386 if self.check_if_phase_completed(INVALID_SNAPSHOT_STATE) {
388 return;
389 }
390
391 let waiter_val = self.phase_waiter.load(Ordering::Acquire);
393
394 if self.check_if_phase_completed(INVALID_SNAPSHOT_STATE) {
396 return;
397 }
398
399 atomic_wait::wait(&self.phase_waiter, waiter_val);
401
402 #[cfg(target_os = "macos")]
404 thread::sleep(std::time::Duration::from_millis(1));
405 }
406 }
407
408 pub fn reserve_thread_slot(&self) -> Result<(usize, u64, PhaseId), ()> {
412 if self.pause_snapshot.load(Ordering::Acquire) {
413 return Err(());
414 }
415
416 let start = get_rng().random_range(0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM);
417 let end = 2 * DEFAULT_MAX_SNAPSHOT_THREAD_NUM;
418
419 for i in start..end {
420 let tid = i % DEFAULT_MAX_SNAPSHOT_THREAD_NUM;
421 if self.thread_slots[tid]
422 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
423 .is_ok()
424 {
425 let global_state = self.global_state.load(Ordering::Acquire);
427 self.set_local_state(&tid, global_state);
428
429 crate::sync::atomic::fence(Ordering::SeqCst);
436
437 let current_global = self.global_state.load(Ordering::Acquire);
447 if self.get_local_state(&tid) != current_global
448 || self.pause_snapshot.load(Ordering::Acquire)
449 {
450 self.set_local_state(&tid, INVALID_SNAPSHOT_STATE);
451 assert!(self.thread_slots[tid]
452 .compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed)
453 .is_ok());
454
455 self.phase_waiter.fetch_add(1, Ordering::Release);
457 atomic_wait::wake_all(&self.phase_waiter);
458
459 return Err(());
460 } else {
461 let version = global_state & SNAPSHOT_STATE_VERSION_MASK;
462 let phase_id = PhaseId::from_raw(
463 (global_state & SNAPSHOT_STATE_PHASE_ID_MASK)
464 >> SNAPSHOT_STATE_PHASE_ID_SHIFT,
465 );
466 return Ok((tid, version, phase_id));
467 }
468 }
469 }
470 Err(())
471 }
472
473 pub fn release_thread_slot(&self, thread_slot_id: usize) {
476 self.set_local_state(&thread_slot_id, INVALID_SNAPSHOT_STATE);
478 self.thread_slots[thread_slot_id].store(false, Ordering::Release);
479
480 self.phase_waiter.fetch_add(1, Ordering::Release);
483 atomic_wait::wake_all(&self.phase_waiter);
484 }
485
486 pub fn get_snapshot_guard(
487 snapshot_mgr: Option<Arc<CPRSnapShotMgr>>,
488 ) -> Result<CPRSnapshotGuard, ()> {
489 CPRSnapshotGuard::new(snapshot_mgr)
490 }
491
492 pub fn snapshot_page(&self, ptr: &[u8], size: usize) -> usize {
498 let vfs = self.vfs.read().unwrap().clone();
500 let offset = vfs.alloc_offset(size);
501
502 vfs.write(offset, ptr);
504
505 offset
507 }
508
509 pub fn snapshot_inner_node(&self, ptr: *const InnerNode, thread_slot_id: usize) {
510 let offset = unsafe { self.snapshot_page((&*ptr).as_slice(), INNER_NODE_SIZE) };
511 let inner_mappings = unsafe { &mut *self.thread_local_inner_mappings.get() };
512
513 inner_mappings[thread_slot_id].push((ptr, offset));
514 }
515
516 pub fn snapshot_mini_page(&self, id: PageID, ptr: &[u8], size: usize, thread_slot_id: usize) {
517 let offset = if size != 0 {
518 self.snapshot_page(ptr, size)
519 } else {
520 NULL_PAGE_LOCATION_OFFSET
522 };
523
524 let mini_mappings = unsafe { &mut *self.thread_local_mini_mappings.get() };
525 mini_mappings[thread_slot_id].push((id, offset));
526
527 let mini_size_mappings = unsafe { &mut *self.thread_local_mini_size_mappings.get() };
528 mini_size_mappings[thread_slot_id].push((id, size));
529
530 assert!(mini_mappings[thread_slot_id].len() == mini_size_mappings[thread_slot_id].len());
531 }
532
533 pub fn snapshot_base_page(&self, id: PageID, ptr: &[u8], size: usize, thread_slot_id: usize) {
534 let offset = self.snapshot_page(ptr, size);
535
536 let base_mappings = unsafe { &mut *self.thread_local_base_mappings.get() };
537 base_mappings[thread_slot_id].push((id, offset));
538 }
539
540 pub fn snapshot_root_page(&self, root_id: PageID) {
541 let cur_root_id = self.root_id.load(Ordering::Acquire);
542 if cur_root_id != 0 {
543 assert_eq!(cur_root_id, root_id.raw());
544 }
545
546 self.root_id.store(root_id.raw(), Ordering::Release);
547 }
548
549 fn sweep(
552 &self,
553 tree: &BfTree,
554 version: u64,
555 inner_mapping: &mut Vec<(*const InnerNode, usize)>,
556 mini_mapping: &mut Vec<(PageID, usize)>,
557 mini_size_mapping: &mut Vec<(PageID, usize)>,
558 base_mapping: &mut Vec<(PageID, usize)>,
559 ) -> usize {
560 self.pause_snapshot.store(true, Ordering::Release);
571 loop {
572 if self.check_if_phase_completed(INVALID_SNAPSHOT_STATE) {
573 loop {
576 let root_id = tree.get_root_page();
577 let rid = root_id.0;
578 if root_id.1 {
579 let mut leaf = tree.mapping_table().get(&rid);
581 let page_loc = leaf.get_page_location();
582
583 match page_loc {
584 PageLocation::Base(offset) => {
585 let base_ref = leaf.load_base_page(*offset);
586 if base_ref.get_clean_snapshot_version() < version {
587 let base_ptr = unsafe {
588 std::slice::from_raw_parts(
589 base_ref as *const LeafNode as *const u8,
590 base_ref.meta.node_size as usize,
591 )
592 };
593 let offset = self
594 .snapshot_page(base_ptr, base_ref.meta.node_size as usize);
595 base_mapping.push((rid, offset));
596 self.snapshot_root_page(rid);
597 }
598 }
599 PageLocation::Mini(ptr) => {
600 assert!(tree.cache_only);
602
603 let mini_ref = leaf.load_cache_page(*ptr);
604 if mini_ref.get_clean_snapshot_version() < version {
605 let mini_ptr = unsafe {
606 std::slice::from_raw_parts(
607 mini_ref as *const LeafNode as *const u8,
608 mini_ref.meta.node_size as usize,
609 )
610 };
611 let offset = self
612 .snapshot_page(mini_ptr, mini_ref.meta.node_size as usize);
613 mini_mapping.push((rid, offset));
614 mini_size_mapping.push((rid, mini_ref.meta.node_size as usize));
615 self.snapshot_root_page(rid);
616 }
617 }
618 _ => {
619 panic!("Unexpected page location for root page: {:?}", page_loc);
620 }
621 }
622
623 break;
624 } else {
625 let ptr = rid.as_inner_node();
627 let inner = match ReadGuard::try_read(ptr) {
629 Ok(inner) => inner,
630 Err(_) => continue,
631 };
632
633 if inner.as_ref().get_clean_snapshot_version() < version {
634 let offset =
635 unsafe { self.snapshot_page((&*ptr).as_slice(), INNER_NODE_SIZE) };
636 inner_mapping.push((ptr, offset));
637 self.snapshot_root_page(rid);
638 }
639 break;
640 }
641 }
642
643 let visitor = BfsVisitor::new_inner_only(tree);
645 for node in visitor {
646 loop {
647 match node {
648 NodeInfo::Inner { ptr, .. } => {
649 let inner = match ReadGuard::try_read(ptr) {
651 Ok(inner) => inner,
652 Err(_) => continue,
653 };
654
655 if inner.as_ref().get_clean_snapshot_version() < version {
656 let offset = unsafe {
657 self.snapshot_page((&*ptr).as_slice(), INNER_NODE_SIZE)
658 };
659 inner_mapping.push((ptr, offset));
660 }
661
662 break;
663 }
664 NodeInfo::Leaf { level, .. } => {
665 assert_eq!(level, 0);
667 break;
668 }
669 }
670 }
671 }
672
673 break;
674 }
675 self.wait_for_all_slots_released();
677
678 #[cfg(all(feature = "shuttle", test))]
679 shuttle::thread::yield_now();
680 }
681
682 self.pause_snapshot.store(false, Ordering::Release);
684
685 let page_table_iter = tree.storage.page_table.iter();
688 let mut enumerate_leaf_count = 0;
689
690 for (_, pid) in page_table_iter {
691 assert!(pid.is_id());
692
693 let mut leaf = tree.mapping_table().get(&pid);
695 let page_loc = leaf.get_page_location().clone();
696 enumerate_leaf_count += 1;
697
698 match page_loc {
699 PageLocation::Base(offset) => {
700 let base_ref = leaf.load_base_page(offset);
701 if base_ref.get_clean_snapshot_version() < version {
702 let base_ptr = unsafe {
703 std::slice::from_raw_parts(
704 base_ref as *const LeafNode as *const u8,
705 base_ref.meta.node_size as usize,
706 )
707 };
708 let new_offset =
709 self.snapshot_page(base_ptr, base_ref.meta.node_size as usize);
710 base_mapping.push((pid, new_offset));
711 }
712 }
713 PageLocation::Full(ptr) => {
714 let full_ref = leaf.load_cache_page(ptr);
716 if full_ref.get_clean_snapshot_version() < version {
717 let next_level = full_ref.next_level;
720 let full_page = unsafe { &mut *ptr };
721 full_page.next_level = MiniPageNextLevel::new_null();
722 let full_ptr = unsafe {
723 std::slice::from_raw_parts(
724 full_ref as *const LeafNode as *const u8,
725 full_ref.meta.node_size as usize,
726 )
727 };
728 let offset = self.snapshot_page(full_ptr, full_ref.meta.node_size as usize);
729 full_page.next_level = next_level;
730 base_mapping.push((pid, offset));
731 }
732 }
733 PageLocation::Mini(ptr) => {
734 let mini_ref = leaf.load_cache_page(ptr);
735 if mini_ref.get_clean_snapshot_version() < version {
736 let mini_ptr = unsafe {
737 std::slice::from_raw_parts(
738 mini_ref as *const LeafNode as *const u8,
739 mini_ref.meta.node_size as usize,
740 )
741 };
742 let offset = self.snapshot_page(mini_ptr, mini_ref.meta.node_size as usize);
743 mini_mapping.push((pid, offset));
744 mini_size_mapping.push((pid, mini_ref.meta.node_size as usize));
745
746 if !tree.cache_only {
747 let base_ref = leaf.load_base_page(mini_ref.next_level.as_offset());
749 assert!(base_ref.get_clean_snapshot_version() < version); let base_ptr = unsafe {
752 std::slice::from_raw_parts(
753 base_ref as *const LeafNode as *const u8,
754 base_ref.meta.node_size as usize,
755 )
756 };
757 let offset =
758 self.snapshot_page(base_ptr, base_ref.meta.node_size as usize);
759 base_mapping.push((pid, offset));
760 }
761 }
762 }
763 PageLocation::Null => {
764 if tree.cache_only {
765 mini_mapping.push((pid, NULL_PAGE_LOCATION_OFFSET)); mini_size_mapping.push((pid, 0));
772 } else {
773 continue;
779 }
780 }
781 }
782 }
783
784 enumerate_leaf_count
785 }
786
787 #[allow(clippy::too_many_arguments)]
789 fn finalize(
790 &self,
791 snapshot_version: u64,
792 inner_mapping: &mut [(*const InnerNode, usize)],
793 mini_mapping: &mut [(PageID, usize)],
794 mini_size_mapping: &mut [(PageID, usize)],
795 base_mapping: &mut [(PageID, usize)],
796 leaf_count_upper: usize,
797 config: Arc<Config>,
798 ) {
799 let mut inner_mapping_unique: HashMap<*const InnerNode, usize> = HashMap::new();
801 let mut mini_mapping_unique: HashMap<PageID, usize> = HashMap::new();
802 let mut mini_size_mapping_unique: HashMap<PageID, usize> = HashMap::new();
803 let mut base_mapping_unique: HashMap<PageID, usize> = HashMap::new();
804
805 let local_inner_mappings = unsafe { &mut *self.thread_local_inner_mappings.get() };
806 let local_mini_mappings = unsafe { &mut *self.thread_local_mini_mappings.get() };
807 let local_mini_size_mappings = unsafe { &mut *self.thread_local_mini_size_mappings.get() };
808 let local_base_mappings = unsafe { &mut *self.thread_local_base_mappings.get() };
809
810 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
811 let entry_num = local_mini_mappings[thread_slot_id].len();
812 assert!(
813 local_mini_mappings[thread_slot_id].len()
814 == local_mini_size_mappings[thread_slot_id].len()
815 );
816 for i in 0..entry_num {
817 assert!(
818 local_mini_mappings[thread_slot_id][i].0
819 == local_mini_size_mappings[thread_slot_id][i].0
820 );
821
822 if local_mini_mappings[thread_slot_id][i].1 == NULL_PAGE_LOCATION_OFFSET {
823 assert_eq!(local_mini_size_mappings[thread_slot_id][i].1, 0);
824 } else {
825 assert!(local_mini_size_mappings[thread_slot_id][i].1 > 0);
826 }
827
828 if let std::collections::hash_map::Entry::Vacant(e) =
829 mini_mapping_unique.entry(local_mini_mappings[thread_slot_id][i].0)
830 {
831 e.insert(local_mini_mappings[thread_slot_id][i].1);
832 mini_size_mapping_unique.insert(
833 local_mini_size_mappings[thread_slot_id][i].0,
834 local_mini_size_mappings[thread_slot_id][i].1,
835 );
836 assert!(mini_mapping_unique.len() == mini_size_mapping_unique.len());
837 }
838 }
839 assert_eq!(local_mini_mappings[thread_slot_id].len(), entry_num);
840 assert_eq!(local_mini_size_mappings[thread_slot_id].len(), entry_num);
841 }
842
843 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
845 for m in local_inner_mappings[thread_slot_id].iter() {
846 inner_mapping_unique.entry(m.0).or_insert(m.1);
847 }
848
849 for m in local_base_mappings[thread_slot_id].iter() {
850 base_mapping_unique.entry(m.0).or_insert(m.1);
851 }
852 }
853
854 assert!(mini_mapping_unique.len() == mini_size_mapping_unique.len());
856 for (k, v) in mini_mapping_unique.iter() {
857 assert!(mini_size_mapping_unique.contains_key(k));
858 if *v == NULL_PAGE_LOCATION_OFFSET {
859 assert_eq!(mini_size_mapping_unique.get(k).copied().unwrap(), 0);
860 } else {
861 assert!(mini_size_mapping_unique.get(k).copied().unwrap() > 0);
862 }
863 }
864
865 for (k, v) in inner_mapping.iter() {
867 if !inner_mapping_unique.contains_key(k) {
868 inner_mapping_unique.insert(*k, *v);
869 }
870 }
871 for (k, v) in mini_mapping.iter() {
872 if !mini_mapping_unique.contains_key(k) {
873 mini_mapping_unique.insert(*k, *v);
874 }
875 }
876 for (k, v) in mini_size_mapping.iter() {
877 if !mini_size_mapping_unique.contains_key(k) {
878 mini_size_mapping_unique.insert(*k, *v);
879 } else {
880 if !config.cache_only {
881 assert!(*v == mini_size_mapping_unique.get(k).copied().unwrap());
882 }
883 }
884 }
885 for (k, v) in base_mapping.iter() {
886 if !base_mapping_unique.contains_key(k) {
887 base_mapping_unique.insert(*k, *v);
888 }
889 }
890
891 assert!(mini_mapping_unique.len() == mini_size_mapping_unique.len());
893 for (k, v) in mini_mapping_unique.iter() {
894 assert!(mini_size_mapping_unique.contains_key(k));
895 if *v == NULL_PAGE_LOCATION_OFFSET {
896 assert_eq!(mini_size_mapping_unique.get(k).copied().unwrap(), 0);
897 } else {
898 assert!(mini_size_mapping_unique.get(k).copied().unwrap() > 0);
899 }
900 }
901
902 if config.cache_only {
903 assert!(base_mapping_unique.is_empty());
904 } else {
905 assert!(mini_mapping_unique.len() <= base_mapping_unique.len());
906 }
907
908 let mut final_inner_mapping: Vec<(*const InnerNode, usize)> = Vec::new();
910 for (k, v) in inner_mapping_unique.into_iter() {
911 final_inner_mapping.push((k, v));
912 }
913
914 let final_sorted_base_mapping: Vec<(PageID, usize)> = if !config.cache_only {
923 let mut final_sorted_base_mapping = base_mapping_unique.into_iter().collect::<Vec<_>>();
924 final_sorted_base_mapping.sort_unstable_by_key(|(pid, _)| {
925 assert!(pid.is_id());
926 pid.as_id()
927 });
928 final_sorted_base_mapping
929 } else {
930 Vec::new()
931 };
932
933 let mut final_mini_mapping: Vec<(PageID, usize)> = Vec::new();
935 for (k, v) in mini_mapping_unique.into_iter() {
936 final_mini_mapping.push((k, v));
937 }
938
939 let mut final_mini_size_mapping: Vec<(PageID, usize)> = Vec::new();
940 for (k, v) in mini_size_mapping_unique.into_iter() {
941 final_mini_size_mapping.push((k, v));
942 }
943
944 let leaf_page_num = if config.cache_only {
945 leaf_count_upper
953 } else {
954 assert!(leaf_count_upper >= final_sorted_base_mapping.len());
955 final_sorted_base_mapping.len()
956 };
957
958 let mut file_size = std::mem::size_of::<BfTreeMeta>() as u64;
959
960 let (inner_offset, inner_size) =
962 serialize_vec_to_disk(&final_inner_mapping, &self.vfs.read().unwrap());
963
964 if inner_offset != 0 {
965 file_size = (inner_offset + align_to_sector_size(inner_size)) as u64;
966 }
967
968 let (mini_offset, mini_size) =
969 serialize_vec_to_disk(&final_mini_mapping, &self.vfs.read().unwrap());
970
971 if mini_offset != 0 {
972 file_size = (mini_offset + align_to_sector_size(mini_size)) as u64;
973 }
974
975 let (mini_size_offset, mini_size_size) =
976 serialize_vec_to_disk(&final_mini_size_mapping, &self.vfs.read().unwrap());
977
978 if mini_size_offset != 0 {
979 file_size = (mini_size_offset + align_to_sector_size(mini_size_size)) as u64;
980 }
981
982 let (base_offset, base_size) =
983 serialize_vec_to_disk(&final_sorted_base_mapping, &self.vfs.read().unwrap());
984
985 if base_offset != 0 {
986 file_size = (base_offset + align_to_sector_size(base_size)) as u64;
987 }
988
989 let metadata = BfTreeMeta {
991 magic_begin: *BF_TREE_MAGIC_BEGIN,
992 root_id: unsafe { PageID::from_raw(self.root_id.load(Ordering::Acquire)) },
993 inner_offset,
994 inner_size,
995 mini_offset,
996 mini_size,
997 mini_size_offset,
998 mini_size_size,
999 base_offset,
1000 base_size,
1001 file_size,
1002 leaf_page_num,
1003 snapshot_version,
1004 cache_only: config.cache_only,
1005 cb_size_byte: config.cb_size_byte,
1006 read_promotion_rate: config.read_promotion_rate.load(Ordering::Relaxed),
1007 scan_promotion_rate: config.scan_promotion_rate.load(Ordering::Relaxed),
1008 cb_min_record_size: config.cb_min_record_size,
1009 cb_max_record_size: config.cb_max_record_size,
1010 leaf_page_size: config.leaf_page_size,
1011 cb_max_key_len: config.cb_max_key_len,
1012 max_fence_len: config.max_fence_len,
1013 cb_copy_on_access_ratio: config.cb_copy_on_access_ratio,
1014 read_record_cache: config.read_record_cache,
1015 max_mini_page_size: config.max_mini_page_size,
1016 mini_page_binary_search: config.mini_page_binary_search,
1017 write_load_full_page: config.write_load_full_page,
1018 magic_end: *BF_TREE_MAGIC_END,
1019 };
1020
1021 let vfs = self.vfs.read().unwrap();
1022 vfs.write(META_DATA_PAGE_OFFSET, metadata.as_slice());
1023 vfs.flush();
1024
1025 self.reset();
1026 }
1027
1028 pub fn snapshot(&self, tree: &BfTree, snapshot_file_path: impl AsRef<Path>) {
1030 if self
1032 .snapshot_in_progress
1033 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
1034 .is_err()
1035 {
1036 println!("Another snapshot is in progress, skipping this snapshot request.");
1037 return;
1038 }
1039
1040 let mut vfs_guard = self.vfs.write().unwrap();
1042 let snapshot_vfs = make_vfs(&tree.config.snapshot_backend, snapshot_file_path);
1043 let old_vfs = std::mem::replace(&mut *vfs_guard, snapshot_vfs);
1044 drop(old_vfs);
1045
1046 vfs_guard.reset();
1048
1049 drop(vfs_guard);
1051
1052 let mut sweep_inner_mapping: Vec<(*const InnerNode, usize)> = Vec::new();
1054 let mut sweep_mini_mapping: Vec<(PageID, usize)> = Vec::new();
1055 let mut sweep_mini_size_mapping: Vec<(PageID, usize)> = Vec::new();
1056 let mut sweep_base_mapping: Vec<(PageID, usize)> = Vec::new();
1057
1058 let mut current_global_phase_id = self.get_global_phase_id();
1060 assert_eq!(current_global_phase_id, PhaseId::Rest);
1061
1062 let snapshot_version = self.get_global_version();
1064
1065 let mut current_global_state = self.advance_global_state();
1067 current_global_phase_id = self.get_global_phase_id();
1068 assert_eq!(current_global_phase_id, PhaseId::Prepare);
1069 assert_eq!(snapshot_version, self.get_global_version());
1070
1071 let mut leaf_node_count_upper_bound = 0; loop {
1074 if self.check_if_phase_completed(current_global_state) {
1075 match current_global_phase_id {
1076 PhaseId::Rest => {
1077 self.finalize(
1082 snapshot_version,
1083 &mut sweep_inner_mapping,
1084 &mut sweep_mini_mapping,
1085 &mut sweep_mini_size_mapping,
1086 &mut sweep_base_mapping,
1087 leaf_node_count_upper_bound,
1088 tree.config.clone(),
1089 );
1090
1091 let mut vfs_guard = self.vfs.write().unwrap();
1093 let snapshot_vfs = make_vfs(&StorageBackend::Memory, ":memory:");
1094 let old_vfs = std::mem::replace(&mut *vfs_guard, snapshot_vfs);
1095 drop(old_vfs);
1096 drop(vfs_guard);
1097
1098 break;
1100 }
1101 PhaseId::Prepare => {
1102 current_global_state = self.advance_global_state();
1103 current_global_phase_id = self.get_global_phase_id();
1104 assert_eq!(current_global_phase_id, PhaseId::InProgress);
1105 assert_eq!(snapshot_version + 1, self.get_global_version());
1106 }
1107 PhaseId::InProgress => {
1108 current_global_state = self.advance_global_state();
1109 current_global_phase_id = self.get_global_phase_id();
1110 assert_eq!(current_global_phase_id, PhaseId::Sweep);
1111 assert_eq!(snapshot_version + 1, self.get_global_version());
1112 }
1113 PhaseId::Sweep => {
1114 leaf_node_count_upper_bound = self.sweep(
1122 tree,
1123 snapshot_version + 1,
1124 &mut sweep_inner_mapping,
1125 &mut sweep_mini_mapping,
1126 &mut sweep_mini_size_mapping,
1127 &mut sweep_base_mapping,
1128 );
1129
1130 current_global_state = self.advance_global_state();
1131 current_global_phase_id = self.get_global_phase_id();
1132 assert_eq!(current_global_phase_id, PhaseId::Rest);
1133 assert_eq!(snapshot_version + 1, self.get_global_version());
1134 }
1135 }
1136 }
1137
1138 self.wait_for_phase_completion(current_global_state);
1141
1142 #[cfg(all(feature = "shuttle", test))]
1143 shuttle::thread::yield_now();
1144 }
1145
1146 assert!(self
1147 .snapshot_in_progress
1148 .compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed)
1149 .is_ok());
1150 }
1151
1152 pub fn new_from_snapshot(
1166 recovery_snapshot_file_path: impl AsRef<Path>, use_snapshot: bool,
1168 buffer_ptr: Option<*mut u8>,
1169 buffer_size: Option<usize>, wal_config: Option<Arc<WalConfig>>,
1171 ) -> Result<BfTree, ConfigError> {
1172 if !recovery_snapshot_file_path.as_ref().exists() {
1174 return Err(ConfigError::SnapshotFileInvalid(
1176 "Not found ".to_string() + recovery_snapshot_file_path.as_ref().to_str().unwrap(),
1177 ));
1178 }
1179
1180 let wal = wal_config.as_ref().map(|s| WriteAheadLog::new(s.clone()));
1182
1183 let reader = std::fs::File::open(recovery_snapshot_file_path.as_ref()).unwrap();
1185 let mut metadata = SectorAlignedVector::new_zeroed(DISK_PAGE_SIZE); #[cfg(unix)]
1187 {
1188 reader.read_at(&mut metadata, 0).unwrap();
1189 }
1190 #[cfg(windows)]
1191 {
1192 reader.seek_read(&mut metadata, 0).unwrap();
1193 }
1194
1195 let bf_meta = unsafe { (metadata.as_ptr() as *const BfTreeMeta).read() };
1196 bf_meta.check_magic();
1197 assert_eq!(reader.metadata().unwrap().len(), bf_meta.file_size);
1198
1199 let mut bf_tree_config = Config::new_from_snapshot(&bf_meta);
1200
1201 let recovery_snapshot_file_backend = StorageBackend::Std; if !bf_tree_config.cache_only {
1203 bf_tree_config.file_path(recovery_snapshot_file_path.as_ref());
1204 bf_tree_config.storage_backend = recovery_snapshot_file_backend.clone();
1206 } else {
1207 bf_tree_config.storage_backend = StorageBackend::Memory;
1208 }
1209 bf_tree_config.use_snapshot = use_snapshot;
1210
1211 bf_tree_config.snapshot_backend = StorageBackend::Std; let snapshot_mgr = if bf_tree_config.use_snapshot {
1214 Some(Arc::new(CPRSnapShotMgr::new(
1215 bf_tree_config.snapshot_version,
1216 )))
1217 } else {
1218 None
1219 };
1220
1221 if let Some(size) = buffer_size {
1222 bf_tree_config.cb_size_byte = size
1223 }
1224
1225 let size_classes = BfTree::create_mem_page_size_classes(
1226 bf_tree_config.cb_min_record_size,
1227 bf_tree_config.cb_max_record_size,
1228 bf_tree_config.leaf_page_size,
1229 bf_tree_config.max_fence_len,
1230 bf_tree_config.cache_only,
1231 );
1232
1233 bf_tree_config.write_ahead_log = wal_config.clone();
1234 bf_tree_config.validate()?;
1235
1236 let config = Arc::new(bf_tree_config);
1237
1238 let recovery_snapshot_vfs = make_vfs(
1240 &recovery_snapshot_file_backend,
1241 recovery_snapshot_file_path.as_ref(),
1242 );
1243
1244 let mut root_page_id = bf_meta.root_id;
1246 let mut inner_node_page_buffer = SectorAlignedVector::new_zeroed(INNER_NODE_SIZE);
1247 if root_page_id.is_inner_node_pointer() {
1248 let inner_mapping: Vec<(*const InnerNode, usize)> = read_vec_from_offset(
1249 bf_meta.inner_offset,
1250 bf_meta.inner_size,
1251 &recovery_snapshot_vfs,
1252 );
1253
1254 let mut root_cnt = 0;
1256 for (_ptr, offset) in &inner_mapping {
1257 recovery_snapshot_vfs.read(*offset, &mut inner_node_page_buffer);
1258 let inner_node = InnerNodeBuilder::new().build_from_slice(&inner_node_page_buffer);
1259 if unsafe { (*inner_node).is_root() } {
1260 root_cnt += 1;
1261 }
1262 InnerNode::free_node(inner_node);
1263 }
1264 assert_eq!(root_cnt, 1, "Root count in inner mapping: {}", root_cnt);
1265
1266 let mut inner_map = HashMap::new();
1267
1268 for m in inner_mapping {
1269 inner_map.insert(m.0, m.1);
1270 }
1271 let offset = inner_map.get(&root_page_id.as_inner_node()).unwrap();
1272 recovery_snapshot_vfs.read(*offset, &mut inner_node_page_buffer);
1273 let root_page = InnerNodeBuilder::new().build_from_slice(&inner_node_page_buffer);
1274
1275 unsafe {
1277 (*root_page).set_disk_offset(INVALID_DISK_OFFSET as u64);
1278 }
1279 unsafe {
1281 (*root_page).set_root(true);
1282 }
1283 root_page_id = PageID::from_pointer(root_page);
1284
1285 let mut inner_resolve_queue = VecDeque::from([root_page]);
1286 while !inner_resolve_queue.is_empty() {
1287 let inner_ptr = inner_resolve_queue.pop_front().unwrap();
1288 let mut inner = ReadGuard::try_read(inner_ptr).unwrap().upgrade().unwrap();
1289 if inner.as_ref().meta.children_is_leaf() {
1290 continue;
1291 }
1292 for (idx, c) in inner.as_ref().get_child_iter().enumerate() {
1293 let offset = inner_map.get(&c.as_inner_node()).unwrap();
1294 recovery_snapshot_vfs.read(*offset, &mut inner_node_page_buffer);
1295 let inner_page =
1296 InnerNodeBuilder::new().build_from_slice(&inner_node_page_buffer);
1297 unsafe {
1298 (*inner_page).set_disk_offset(INVALID_DISK_OFFSET as u64);
1299 }
1300 let inner_id = PageID::from_pointer(inner_page);
1301 inner.as_mut().update_at_pos(idx, inner_id);
1302 inner_resolve_queue.push_back(inner_page);
1303 }
1304 }
1305 }
1306
1307 let raw_root_id = if root_page_id.is_id() {
1308 root_page_id.raw() | BfTree::ROOT_IS_LEAF_MASK
1309 } else {
1310 root_page_id.raw()
1311 };
1312
1313 if !bf_meta.cache_only {
1316 let mut base_mapping: Vec<(PageID, usize)> = if bf_meta.base_size > 0 {
1318 read_vec_from_offset(
1319 bf_meta.base_offset,
1320 bf_meta.base_size,
1321 &recovery_snapshot_vfs,
1322 )
1323 } else {
1324 Vec::new()
1325 };
1326
1327 base_mapping.sort_unstable_by_key(|(pid, _)| {
1337 assert!(pid.is_id());
1338 pid.as_id()
1339 });
1340
1341 let dense_base_page_loc_mapping: Vec<(PageID, PageLocation)> =
1342 if let Some(max_pid) = base_mapping.last().map(|(pid, _)| pid.as_id()) {
1343 let mut dense_base_page_loc_mapping = Vec::with_capacity(max_pid as usize + 1);
1344 let mut base_mapping_iter = base_mapping.into_iter().peekable();
1345
1346 for raw_pid in 0..=max_pid {
1347 let pid = PageID::from_id(raw_pid);
1348 let loc = if matches!(
1349 base_mapping_iter.peek(),
1350 Some((next_pid, _)) if next_pid.as_id() == raw_pid
1351 ) {
1352 let (_, offset) = base_mapping_iter.next().unwrap();
1353 PageLocation::Base(offset)
1354 } else {
1355 PageLocation::Null
1356 };
1357 dense_base_page_loc_mapping.push((pid, loc));
1358 }
1359
1360 dense_base_page_loc_mapping
1361 } else {
1362 Vec::new()
1363 };
1364
1365 let pt = PageTable::new_from_mapping(
1367 dense_base_page_loc_mapping.into_iter(),
1368 recovery_snapshot_vfs.clone(),
1369 config.clone(),
1370 snapshot_mgr.clone(),
1371 );
1372
1373 let circular_buffer = CircularBuffer::new(
1374 config.cb_size_byte,
1375 config.cb_copy_on_access_ratio,
1376 config.cb_min_record_size,
1377 config.cb_max_record_size,
1378 config.leaf_page_size,
1379 config.max_fence_len,
1380 buffer_ptr,
1381 config.cache_only,
1382 );
1383
1384 let mini_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size > 0 {
1386 read_vec_from_offset(
1387 bf_meta.mini_offset,
1388 bf_meta.mini_size,
1389 &recovery_snapshot_vfs,
1390 )
1391 } else {
1392 Vec::new()
1393 };
1394
1395 let mini_size_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size_size > 0 {
1396 read_vec_from_offset(
1397 bf_meta.mini_size_offset,
1398 bf_meta.mini_size_size,
1399 &recovery_snapshot_vfs,
1400 )
1401 } else {
1402 Vec::new()
1403 };
1404
1405 let mut mini_size_mapping_unique: HashMap<PageID, usize> = HashMap::new();
1406 for (pid, size) in mini_size_mapping {
1407 mini_size_mapping_unique.insert(pid, size);
1408 }
1409
1410 let storage =
1412 LeafStorage::new_inner(config.clone(), pt, circular_buffer, recovery_snapshot_vfs);
1413
1414 for (pid, offset) in &mini_mapping {
1415 let mini_size = *mini_size_mapping_unique.get(pid).unwrap();
1416
1417 let mini_page_guard = match storage.alloc_mini_page(mini_size) {
1419 Ok(mini_page_ptr) => mini_page_ptr,
1420 Err(_) => {
1421 return Err(ConfigError::CircularBufferSize("buffer size set too small. Consider increasing it or not specifying at all".to_string()));
1422 }
1423 };
1424
1425 let mut page_buffer = SectorAlignedVector::new_zeroed(mini_size);
1427 storage.vfs.read(*offset, &mut page_buffer);
1428 unsafe {
1429 std::ptr::copy_nonoverlapping(
1430 page_buffer.as_ptr(),
1431 mini_page_guard.as_ptr(),
1432 mini_size,
1433 );
1434 }
1435
1436 let new_mini_ptr = mini_page_guard.as_ptr() as *mut LeafNode;
1438 let mini_page = unsafe { &mut *new_mini_ptr };
1439
1440 let mut base_page = storage.page_table.get_mut(pid);
1441 let page_loc = base_page.get_page_location().clone();
1442 match page_loc {
1443 PageLocation::Base(off) => {
1444 mini_page.next_level = MiniPageNextLevel::new(off);
1445 }
1446 _ => {
1447 panic!("Unexpected page location for base page");
1448 }
1449 }
1450 let mini_loc = PageLocation::Mini(new_mini_ptr);
1451
1452 base_page.create_cache_page_loc(mini_loc);
1454 }
1455
1456 Ok(BfTree {
1457 storage,
1458 root_page_id: AtomicU64::new(raw_root_id),
1459 wal,
1460 write_load_full_page: config.write_load_full_page,
1461 cache_only: false,
1462 mini_page_size_classes: size_classes,
1463 snapshot_mgr,
1464 config,
1465 #[cfg(any(feature = "metrics-rt-debug-all", feature = "metrics-rt-debug-timer"))]
1466 metrics_recorder: Some(Arc::new(ThreadLocal::new())),
1467 })
1468 } else {
1469 let mini_mapping_unallocated: Vec<(PageID, PageLocation)> = (0..bf_meta.leaf_page_num)
1471 .map(|pid| (PageID::from_id(pid as u64), PageLocation::Null))
1472 .collect();
1473
1474 let mini_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size > 0 {
1476 read_vec_from_offset(
1477 bf_meta.mini_offset,
1478 bf_meta.mini_size,
1479 &recovery_snapshot_vfs,
1480 )
1481 } else {
1482 Vec::new()
1483 };
1484
1485 let mini_size_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size_size > 0 {
1486 read_vec_from_offset(
1487 bf_meta.mini_size_offset,
1488 bf_meta.mini_size_size,
1489 &recovery_snapshot_vfs,
1490 )
1491 } else {
1492 Vec::new()
1493 };
1494
1495 let mut mini_size_mapping_unique: HashMap<PageID, usize> = HashMap::new();
1496 for (pid, size) in mini_size_mapping {
1497 mini_size_mapping_unique.insert(pid, size);
1498 }
1499
1500 let storage_vfs = make_vfs(&config.storage_backend, PathBuf::new());
1502
1503 let pt = PageTable::new_from_mapping(
1504 mini_mapping_unallocated.into_iter(),
1505 storage_vfs.clone(),
1506 config.clone(),
1507 snapshot_mgr.clone(),
1508 );
1509
1510 let circular_buffer = CircularBuffer::new(
1511 config.cb_size_byte,
1512 config.cb_copy_on_access_ratio,
1513 config.cb_min_record_size,
1514 config.cb_max_record_size,
1515 config.leaf_page_size,
1516 config.max_fence_len,
1517 buffer_ptr,
1518 config.cache_only,
1519 );
1520
1521 let storage = LeafStorage::new_inner(config.clone(), pt, circular_buffer, storage_vfs);
1523
1524 for (pid, offset) in &mini_mapping {
1525 let mini_size = *mini_size_mapping_unique.get(pid).unwrap();
1526
1527 if *offset == NULL_PAGE_LOCATION_OFFSET {
1529 continue;
1530 }
1531
1532 let mini_page_guard = match storage.alloc_mini_page(mini_size) {
1534 Ok(mini_page_ptr) => mini_page_ptr,
1535 Err(_) => {
1536 panic!("Please increase cb_size_byte in config");
1537 }
1538 };
1539
1540 let mut page_buffer = SectorAlignedVector::new_zeroed(mini_size);
1542 recovery_snapshot_vfs.read(*offset, &mut page_buffer);
1543 unsafe {
1544 std::ptr::copy_nonoverlapping(
1545 page_buffer.as_ptr(),
1546 mini_page_guard.as_ptr(),
1547 mini_size,
1548 );
1549 }
1550
1551 let mini_page_ptr = mini_page_guard.as_ptr() as *mut LeafNode;
1553 let mini_page = unsafe { &mut *mini_page_ptr };
1554 mini_page.next_level = MiniPageNextLevel::new_null();
1555
1556 let mut null_page = storage.page_table.get_mut(pid);
1558 let page_loc = null_page.get_page_location().clone();
1559 match page_loc {
1560 PageLocation::Null => {
1561 let mini_loc = PageLocation::Mini(mini_page_ptr);
1562 null_page.create_cache_page_loc(mini_loc);
1563 }
1564 _ => {
1565 panic!("Unexpected page location for null page");
1566 }
1567 }
1568 }
1569 Ok(BfTree {
1570 storage,
1571 root_page_id: AtomicU64::new(raw_root_id),
1572 wal,
1573 write_load_full_page: config.write_load_full_page,
1574 cache_only: true,
1575 mini_page_size_classes: size_classes,
1576 snapshot_mgr,
1577 config,
1578 #[cfg(any(feature = "metrics-rt-debug-all", feature = "metrics-rt-debug-timer"))]
1579 metrics_recorder: Some(Arc::new(ThreadLocal::new())),
1580 })
1581 }
1582 }
1583}
1584
1585impl BfTree {
1586 pub fn recovery(
1589 recovery_snapshot_file_path: PathBuf, wal_file: impl AsRef<Path>,
1591 use_snapshot: bool,
1592 buffer_ptr: Option<*mut u8>,
1593 buffer_size: Option<usize>,
1594 wal: Option<Arc<WalConfig>>,
1595 ) {
1596 let bf_tree = BfTree::new_from_cpr_snapshot(
1597 recovery_snapshot_file_path,
1598 use_snapshot,
1599 buffer_ptr,
1600 buffer_size,
1601 wal,
1602 )
1603 .unwrap();
1604 let wal_reader = WalReader::new(wal_file, 4096);
1605
1606 for seg in wal_reader.segment_iter() {
1607 for entry in seg.entry_iter() {
1608 let log_entry = LogEntry::read_from_buffer(entry.1);
1609 match log_entry {
1610 LogEntry::Write(op) => {
1611 bf_tree.insert(op.key, op.value);
1612 }
1613 LogEntry::Split(_op) => {
1614 todo!("implement split op in wal!")
1615 }
1616 }
1617 }
1618 }
1619 }
1620
1621 pub fn cpr_snapshot(&self, snapshot_file_path: impl AsRef<Path>) {
1623 if !self.config.use_snapshot {
1624 panic!("Snapshots are not enabled in the configuration");
1625 }
1626
1627 let snpshot_mgr = self.snapshot_mgr.clone().unwrap();
1628 snpshot_mgr.snapshot(self, snapshot_file_path);
1629 }
1630
1631 pub fn new_from_cpr_snapshot(
1633 recovery_snapshot_file_path: impl AsRef<Path>, use_snapshot: bool,
1635 buffer_ptr: Option<*mut u8>,
1636 buffer_size: Option<usize>,
1637 wal: Option<Arc<WalConfig>>,
1638 ) -> Result<BfTree, ConfigError> {
1639 CPRSnapShotMgr::new_from_snapshot(
1640 recovery_snapshot_file_path,
1641 use_snapshot,
1642 buffer_ptr,
1643 buffer_size,
1644 wal,
1645 )
1646 }
1647
1648 pub fn are_all_threads_in_next_snapshot_version(&self) -> bool {
1650 if let Some(snapshot_mgr) = &self.snapshot_mgr {
1651 return snapshot_mgr.are_all_threads_in_next_version();
1652 }
1653 false
1654 }
1655}
1656
1657struct SectorAlignedVector {
1658 inner: ManuallyDrop<Vec<u8>>,
1659}
1660
1661impl Drop for SectorAlignedVector {
1662 fn drop(&mut self) {
1663 let layout =
1664 std::alloc::Layout::from_size_align(self.inner.capacity(), SECTOR_SIZE).unwrap();
1665 let ptr = self.inner.as_mut_ptr();
1666 unsafe {
1667 std::alloc::dealloc(ptr, layout);
1668 }
1669 }
1670}
1671
1672impl SectorAlignedVector {
1673 fn new_zeroed(capacity: usize) -> Self {
1674 let layout = std::alloc::Layout::from_size_align(capacity, SECTOR_SIZE).unwrap();
1675 let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
1676
1677 let inner = unsafe { Vec::from_raw_parts(ptr, capacity, capacity) };
1678 Self {
1679 inner: ManuallyDrop::new(inner),
1680 }
1681 }
1682}
1683
1684impl Deref for SectorAlignedVector {
1685 type Target = Vec<u8>;
1686
1687 fn deref(&self) -> &Self::Target {
1688 &self.inner
1689 }
1690}
1691
1692impl DerefMut for SectorAlignedVector {
1693 fn deref_mut(&mut self) -> &mut Self::Target {
1694 &mut self.inner
1695 }
1696}
1697
1698#[repr(C, align(512))]
1702pub(crate) struct BfTreeMeta {
1703 magic_begin: [u8; 16],
1704 root_id: PageID,
1706 inner_offset: usize,
1707 inner_size: usize,
1708 mini_offset: usize,
1709 mini_size: usize,
1710 mini_size_offset: usize,
1711 mini_size_size: usize,
1712 base_offset: usize,
1713 base_size: usize,
1714 file_size: u64,
1715 leaf_page_num: usize,
1716 pub(crate) cb_size_byte: usize,
1718 pub(crate) snapshot_version: u64,
1719 pub(crate) cache_only: bool,
1720 pub(crate) read_promotion_rate: usize,
1721 pub(crate) scan_promotion_rate: usize,
1722 pub(crate) cb_min_record_size: usize,
1723 pub(crate) cb_max_record_size: usize,
1724 pub(crate) leaf_page_size: usize,
1725 pub(crate) cb_max_key_len: usize,
1726 pub(crate) max_fence_len: usize,
1727 pub(crate) cb_copy_on_access_ratio: f64,
1728 pub(crate) read_record_cache: bool,
1729 pub(crate) max_mini_page_size: usize,
1730 pub(crate) mini_page_binary_search: bool,
1731 pub(crate) write_load_full_page: bool,
1732 magic_end: [u8; 14],
1733}
1734const _: () = assert!(std::mem::size_of::<BfTreeMeta>() <= DISK_PAGE_SIZE);
1735
1736impl BfTreeMeta {
1737 fn as_slice(&self) -> &[u8] {
1738 let ptr = self as *const Self as *const u8;
1739 let size = std::mem::size_of::<Self>();
1740 unsafe { std::slice::from_raw_parts(ptr, size) }
1741 }
1742
1743 fn check_magic(&self) {
1744 assert_eq!(self.magic_begin, *BF_TREE_MAGIC_BEGIN);
1745 assert_eq!(self.magic_end, *BF_TREE_MAGIC_END);
1746 }
1747}
1748
1749fn serialize_vec_to_disk<T>(v: &[T], vfs: &Arc<dyn VfsImpl>) -> (usize, usize) {
1751 if v.is_empty() {
1752 return (0, 0);
1753 }
1754 let unaligned_ptr = v.as_ptr() as *const u8;
1755 let unaligned_size = std::mem::size_of_val(v);
1756
1757 let aligned_size = align_to_sector_size(unaligned_size);
1758 let layout = std::alloc::Layout::from_size_align(aligned_size, SECTOR_SIZE).unwrap();
1759 unsafe {
1760 let aligned_ptr = std::alloc::alloc_zeroed(layout);
1761 std::ptr::copy_nonoverlapping(unaligned_ptr, aligned_ptr, unaligned_size);
1762 let slice = std::slice::from_raw_parts(aligned_ptr, aligned_size);
1763 let offset = serialize_u8_slice_to_disk(slice, vfs);
1764 std::alloc::dealloc(aligned_ptr, layout);
1765 (offset, unaligned_size)
1766 }
1767}
1768
1769fn read_vec_from_offset<T: Clone>(offset: usize, size: usize, vfs: &Arc<dyn VfsImpl>) -> Vec<T> {
1770 assert!(size > 0);
1771 let slice = read_u8_slice_from_disk(offset, size, vfs);
1772 let ptr = slice.as_ptr() as *const T;
1773 let size = size / std::mem::size_of::<T>();
1774 let slice = unsafe { std::slice::from_raw_parts(ptr, size) };
1775 slice.to_vec()
1776}
1777
1778fn read_u8_slice_from_disk(offset: usize, size: usize, vfs: &Arc<dyn VfsImpl>) -> Vec<u8> {
1779 let mut res = Vec::new();
1780 let mut buffer = vec![0; DISK_PAGE_SIZE];
1781 for i in (0..size).step_by(DISK_PAGE_SIZE) {
1782 vfs.read(offset + i, &mut buffer); res.extend_from_slice(&buffer);
1784 }
1785 res
1786}
1787
1788const SECTOR_SIZE: usize = 512;
1789
1790fn align_to_sector_size(n: usize) -> usize {
1791 (n + SECTOR_SIZE - 1) & !(SECTOR_SIZE - 1)
1792}
1793
1794fn serialize_u8_slice_to_disk(slice: &[u8], vfs: &Arc<dyn VfsImpl>) -> usize {
1798 let mut start_offset = None;
1799 for chunk in slice.chunks(DISK_PAGE_SIZE) {
1800 let offset = vfs.alloc_offset(DISK_PAGE_SIZE); if start_offset.is_none() {
1802 start_offset = Some(offset);
1803 }
1804 vfs.write(offset, chunk);
1805 }
1806 start_offset.unwrap()
1807}
1808
1809#[cfg(miri)]
1810mod cpr_handshake_miri {
1811 use super::*;
1812 use std::sync::atomic::Ordering;
1813 use std::sync::mpsc;
1814 use std::sync::Arc;
1815 use std::thread;
1816
1817 const ITERATIONS: usize = 300;
1821
1822 struct SendPtr<T>(*mut T);
1831
1832 impl<T> Clone for SendPtr<T> {
1833 fn clone(&self) -> Self {
1834 *self
1835 }
1836 }
1837
1838 impl<T> Copy for SendPtr<T> {}
1839
1840 unsafe impl<T> Send for SendPtr<T> {}
1841
1842 #[test]
1845 fn phase_can_complete_while_a_reservation_is_still_in_the_old_phase() {
1846 for iteration in 0..ITERATIONS {
1847 let mgr = Arc::new(CPRSnapShotMgr::new(0));
1848 let old_state = mgr.global_state.load(Ordering::Relaxed);
1849
1850 let worker_mgr = mgr.clone();
1851 let worker = thread::spawn(move || worker_mgr.reserve_thread_slot().ok());
1852
1853 let new_state = mgr.advance_global_state();
1854 let phase_completed = mgr.check_if_phase_completed(new_state);
1855
1856 let Some((tid, version, phase_id)) = worker.join().unwrap() else {
1857 continue;
1858 };
1859
1860 mgr.release_thread_slot(tid);
1861
1862 let committed = CPRSnapShotMgr::new_snapshot_state(phase_id.as_raw(), version);
1863 assert!(
1864 !(phase_completed && committed == old_state),
1865 "iteration {iteration}: worker committed to the old state {old_state:#x}, \
1866 but the manager advanced to {new_state:#x} and reported the phase complete"
1867 );
1868 }
1869 }
1870
1871 #[test]
1874 fn sweep_freeze_can_admit_a_reservation_after_declaring_the_tree_frozen() {
1875 struct InnerNodeStandIn {
1876 magic: u64,
1877 }
1878
1879 const MAGIC: u64 = 0x5AFE_0000_5AFE;
1880
1881 for _ in 0..ITERATIONS {
1882 let mgr = Arc::new(CPRSnapShotMgr::new(0));
1883 let node = SendPtr(Box::into_raw(Box::new(InnerNodeStandIn { magic: MAGIC })));
1884
1885 let (decided_tx, decided_rx) = mpsc::channel::<()>();
1889
1890 let worker_mgr = mgr.clone();
1891 let worker = thread::spawn(move || {
1892 let node = node;
1895
1896 let Ok((tid, _, _)) = worker_mgr.reserve_thread_slot() else {
1897 return;
1898 };
1899
1900 let _ = decided_rx.recv();
1903 let n = unsafe { &*node.0 };
1904 assert_eq!(n.magic, MAGIC);
1905
1906 worker_mgr.release_thread_slot(tid);
1907 });
1908
1909 mgr.pause_snapshot.store(true, Ordering::Release);
1911 let frozen = mgr.check_if_phase_completed(INVALID_SNAPSHOT_STATE);
1912 if frozen {
1913 drop(unsafe { Box::from_raw(node.0) });
1914 }
1915
1916 let _ = decided_tx.send(());
1918
1919 worker.join().unwrap();
1920
1921 if !frozen {
1922 drop(unsafe { Box::from_raw(node.0) });
1923 }
1924 }
1925 }
1926}
1927
1928#[cfg(test)]
1929mod tests {
1930 use crate::{nodes::leaf_node::LeafReadResult, sync::thread, BfTree, Config};
1931 use std::panic;
1932 #[cfg(feature = "shuttle")]
1933 use std::path::PathBuf;
1934 use std::str::FromStr;
1935 use std::sync::atomic::Ordering;
1936 use std::sync::{atomic::AtomicBool, Arc};
1937
1938 #[test]
1942 fn cpr_snapshot_disk() {
1943 let min_record_size: usize = 64;
1944 let max_record_size: usize = 2408;
1945 let leaf_page_size: usize = 8192;
1946 let snapshot_num: usize = 10;
1947 let num_threads: usize = 4;
1948 let file_path: String = "target/test_simple.bftree".to_string();
1949 let snapshot_file_path: String = "target/test_simple_snapshot.bftree".to_string();
1950
1951 let tmp_file_path = std::path::PathBuf::from_str(&file_path).unwrap();
1952 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
1953
1954 let mut config = Config::new(&tmp_file_path, 128 * 1024); config.storage_backend(crate::StorageBackend::Std);
1956 config.cb_min_record_size = min_record_size + 2 * std::mem::size_of::<usize>();
1957 config.cb_max_record_size = max_record_size;
1958 config.leaf_page_size = leaf_page_size;
1959 config.max_fence_len = min_record_size + 2 * std::mem::size_of::<usize>();
1960 config.use_snapshot(true);
1961
1962 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
1963 let finish = Arc::new(AtomicBool::new(false));
1964
1965 let handles: Vec<_> = (0..num_threads)
1966 .map(|i| {
1967 let finish_clone = finish.clone();
1968 let bftree_clone = bftree.clone();
1969
1970 thread::spawn(move || {
1971 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
1972 assert!(key_len * 2 <= max_record_size);
1973 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
1974
1975 let mut r: usize = 0;
1976 while !finish_clone.load(Ordering::Relaxed) {
1977 key_buffer.fill(r);
1978 key_buffer[0] = i;
1979
1980 match bftree_clone.insert(
1981 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1982 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1983 ) {
1984 crate::LeafInsertResult::Success => {}
1985 _ => {
1986 panic!("Insert failed");
1987 }
1988 }
1989 r += 1;
1990 }
1991 r
1992 })
1993 })
1994 .collect();
1995
1996 thread::sleep(std::time::Duration::from_secs(5));
1997 for _ in 0..snapshot_num {
1998 let _ = std::fs::remove_file(&tmp_snapshot_file_path);
2000 bftree.cpr_snapshot(&tmp_snapshot_file_path);
2001 thread::sleep(std::time::Duration::from_secs(5));
2002 }
2003
2004 let mut rs = vec![0usize; num_threads];
2006 finish.store(true, Ordering::Relaxed);
2007 for (i, h) in handles.into_iter().enumerate() {
2008 let r = h.join().unwrap();
2009 rs[i] = r;
2010 }
2011
2012 verify_snapshot_recovery(
2013 &tmp_snapshot_file_path,
2014 num_threads,
2015 min_record_size,
2016 &rs,
2017 true,
2018 );
2019
2020 std::fs::remove_file(tmp_file_path).unwrap();
2021 std::fs::remove_file(tmp_snapshot_file_path).unwrap();
2022 }
2023
2024 #[test]
2026 fn cpr_snapshot_cache_only() {
2027 let min_record_size: usize = 64;
2028 let max_record_size: usize = 2408;
2029 let leaf_page_size: usize = 8192;
2030 let num_threads: usize = 4;
2031
2032 let snapshot_file_path: String =
2033 "target/test_simple_cache_only_snapshot.bftree".to_string();
2034 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
2035
2036 let mut config = Config::default(); config.storage_backend(crate::StorageBackend::Memory);
2038 config.file_path(":memory:");
2039 config.cache_only = true;
2040 config.cb_size_byte(1024 * 1024 * 1024);
2041 config.cb_min_record_size = min_record_size;
2042 config.cb_max_record_size = max_record_size;
2043 config.leaf_page_size = leaf_page_size;
2044 config.max_fence_len = max_record_size;
2045 config.use_snapshot(true);
2046
2047 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
2048 let finish = Arc::new(AtomicBool::new(false));
2049
2050 let handles: Vec<_> = (0..num_threads)
2051 .map(|i| {
2052 let finish_clone = finish.clone();
2053 let bftree_clone = bftree.clone();
2054
2055 thread::spawn(move || {
2056 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
2057 assert!(key_len * 2 <= max_record_size);
2058 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
2059
2060 let mut r: usize = 0;
2061 while !finish_clone.load(Ordering::Relaxed) {
2062 key_buffer.fill(r);
2063 key_buffer[0] = i;
2064
2065 match bftree_clone.insert(
2066 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2067 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2068 ) {
2069 crate::LeafInsertResult::Success => {}
2070 _ => {
2071 panic!("Insert failed");
2072 }
2073 }
2074 r += 1;
2075 }
2076 r
2077 })
2078 })
2079 .collect();
2080
2081 thread::sleep(std::time::Duration::from_secs(5));
2082 bftree.cpr_snapshot(&tmp_snapshot_file_path);
2084 thread::sleep(std::time::Duration::from_secs(5));
2085
2086 let mut rs = vec![0usize; num_threads];
2088 finish.store(true, Ordering::Relaxed);
2089 for (i, h) in handles.into_iter().enumerate() {
2090 let r = h.join().unwrap();
2091 rs[i] = r;
2092 }
2093
2094 verify_snapshot_recovery(
2095 &tmp_snapshot_file_path,
2096 num_threads,
2097 min_record_size,
2098 &rs,
2099 false,
2100 );
2101
2102 std::fs::remove_file(tmp_snapshot_file_path).unwrap();
2103 }
2104
2105 fn verify_snapshot_recovery(
2106 snapshot_file: impl AsRef<std::path::Path>,
2107 num_threads: usize,
2108 min_record_size: usize,
2109 records_num_per_threads: &Vec<usize>,
2110 check_prefix: bool,
2111 ) {
2112 let bftree = BfTree::new_from_cpr_snapshot(snapshot_file, false, None, None, None)
2113 .expect("fail to recover from snapshot");
2114
2115 let mut rs_captured = vec![0usize; num_threads];
2116 for i in 0..num_threads {
2117 let record_num = records_num_per_threads[i];
2118
2119 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
2120 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
2121 let mut res_buffer = vec![0u8; key_len];
2122 let mut not_included = false;
2123 let mut first_gap_record: Option<usize> = None;
2124
2125 for r in 0..record_num {
2126 key_buffer.fill(r);
2127 key_buffer[0] = i;
2128
2129 match bftree.read(
2130 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2131 &mut res_buffer,
2132 ) {
2133 LeafReadResult::Found(v) => {
2134 if check_prefix && not_included {
2135 let mut gaps = Vec::new();
2137 let mut found_after = Vec::new();
2138 let gap_start = first_gap_record.unwrap();
2139 let scan_end = std::cmp::min(r + 50, record_num);
2141 for scan_r in gap_start..scan_end {
2142 key_buffer.fill(scan_r);
2143 key_buffer[0] = i;
2144 match bftree.read(
2145 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2146 &mut res_buffer,
2147 ) {
2148 LeafReadResult::Found(_) => {
2149 found_after.push(scan_r);
2150 }
2151 LeafReadResult::NotFound => {
2152 gaps.push(scan_r);
2153 }
2154 _ => {}
2155 }
2156 }
2157 panic!(
2158 "PREFIX VIOLATION: thread={}, first_gap_at={}, found_record_after_gap={}, \
2159 total_captured_before_gap={}, total_records={}\n\
2160 Gaps in [{}, {}): {:?}\n\
2161 Found in [{}, {}): {:?}",
2162 i, gap_start, r, rs_captured[i], record_num,
2163 gap_start, scan_end, &gaps[..std::cmp::min(gaps.len(), 20)],
2164 gap_start, scan_end, &found_after[..std::cmp::min(found_after.len(), 20)],
2165 );
2166 }
2167 assert_eq!(v as usize, key_len);
2168 assert_eq!(
2169 &res_buffer,
2170 bytemuck::must_cast_slice::<usize, u8>(&key_buffer)
2171 );
2172 rs_captured[i] += 1;
2173 }
2174 LeafReadResult::NotFound => {
2175 if !not_included {
2176 not_included = true;
2177 first_gap_record = Some(r);
2178 }
2179 }
2180 _ => {
2181 panic!("Unexpected read result")
2182 }
2183 }
2184 }
2185
2186 assert!(rs_captured[i] <= record_num);
2187 println!("Total inserted records for thread {}: {}", i, record_num);
2188 println!(
2189 "Hit ratio for thread {}: {}",
2190 i,
2191 rs_captured[i] as f64 / record_num as f64
2192 );
2193 }
2194 }
2195
2196 #[cfg(feature = "shuttle")]
2200 fn shuttle_cpr_snapshot_cache_only_inner(iter: usize) {
2201 let min_record_size: usize = 64;
2202 let max_record_size: usize = 2408;
2203 let leaf_page_size: usize = 8192;
2204 let num_threads: usize = 4;
2205 let inserts_per_thread: usize = 1_000; let snapshot_file_path: String = format!(
2209 "target/shuttle_cpr_snapshot_cache_only_{}_{}.bftree",
2210 std::process::id(),
2211 iter,
2212 );
2213 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
2214
2215 let mut config = Config::default();
2216 config.storage_backend(crate::StorageBackend::Memory);
2217 config.file_path(":memory:");
2218 config.cache_only = true;
2219 config.cb_size_byte(1024 * 1024 * 1024);
2222 config.cb_min_record_size = min_record_size;
2223 config.cb_max_record_size = max_record_size;
2224 config.leaf_page_size = leaf_page_size;
2225 config.max_fence_len = max_record_size;
2226 config.use_snapshot(true);
2227
2228 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
2229 let mut rs = vec![0usize; num_threads];
2230
2231 for j in 0..2 {
2232 let handles: Vec<_> = (0..num_threads)
2233 .map(|i| {
2234 let bftree_clone = bftree.clone();
2235 let start_id = j * inserts_per_thread;
2236 let end_id = start_id + inserts_per_thread;
2237 thread::spawn(move || {
2238 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
2239 assert!(key_len * 2 <= max_record_size);
2240 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
2241
2242 for r in start_id..end_id {
2243 key_buffer.fill(r);
2244 key_buffer[0] = i;
2245
2246 match bftree_clone.insert(
2247 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2248 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2249 ) {
2250 crate::LeafInsertResult::Success => {}
2251 _ => {
2252 panic!("Insert failed");
2253 }
2254 }
2255 }
2256 inserts_per_thread
2257 })
2258 })
2259 .collect();
2260
2261 let bftree_for_snap = bftree.clone();
2263 let snap_path = tmp_snapshot_file_path.clone();
2264 let snap_handle = thread::spawn(move || {
2265 bftree_for_snap.cpr_snapshot(&snap_path);
2266 });
2267
2268 for (i, h) in handles.into_iter().enumerate() {
2269 let r = h.join().unwrap();
2270 rs[i] += r;
2271 }
2272
2273 snap_handle.join().unwrap();
2275 let snap_path = tmp_snapshot_file_path.clone();
2276 verify_snapshot_recovery(&snap_path, num_threads, min_record_size, &rs, false);
2277 }
2278
2279 let _ = std::fs::remove_file(tmp_snapshot_file_path);
2280 }
2281
2282 #[cfg(feature = "shuttle")]
2284 #[test]
2285 fn shuttle_cpr_snapshot_cache_only() {
2286 use std::sync::atomic::AtomicUsize;
2287
2288 static ITER: AtomicUsize = AtomicUsize::new(0);
2291
2292 let mut shuttle_config = shuttle::Config::default();
2293 shuttle_config.max_steps = shuttle::MaxSteps::None;
2295 shuttle_config.stack_size = 1024 * 1024 * 1024; shuttle_config.failure_persistence =
2297 shuttle::FailurePersistence::File(Some(PathBuf::from_str("target").unwrap()));
2298
2299 let mut runner = shuttle::PortfolioRunner::new(true, shuttle_config);
2300 let available_cores = std::thread::available_parallelism().unwrap().get().min(4);
2301 for _ in 0..available_cores {
2302 runner.add(shuttle::scheduler::PctScheduler::new(10, 1000));
2303 }
2304
2305 runner.run(|| {
2306 let iter = ITER.fetch_add(1, Ordering::Relaxed);
2307 shuttle_cpr_snapshot_cache_only_inner(iter);
2308 eprintln!("Completed shuttle iteration {}", iter);
2309 });
2310 }
2311
2312 #[cfg(feature = "shuttle")]
2316 fn shuttle_cpr_snapshot_disk_inner(iter: usize) {
2317 let min_record_size: usize = 64;
2318 let max_record_size: usize = 2408;
2319 let leaf_page_size: usize = 8192;
2320 let num_threads: usize = 4;
2321 let inserts_per_thread: usize = 500;
2325
2326 let file_path: String = format!(
2327 "target/shuttle_cpr_snapshot_disk_{}_{}.bftree",
2328 std::process::id(),
2329 iter,
2330 );
2331 let snapshot_file_path: String = format!(
2332 "target/shuttle_cpr_snapshot_disk_{}_{}_snap.bftree",
2333 std::process::id(),
2334 iter,
2335 );
2336 let tmp_file_path = std::path::PathBuf::from_str(&file_path).unwrap();
2337 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
2338
2339 let mut config = Config::new(&tmp_file_path, 128 * 1024); config.storage_backend(crate::StorageBackend::Std);
2341 config.cb_min_record_size = min_record_size + 2 * std::mem::size_of::<usize>();
2342 config.cb_max_record_size = max_record_size;
2343 config.leaf_page_size = leaf_page_size;
2344 config.max_fence_len = min_record_size + 2 * std::mem::size_of::<usize>();
2345 config.use_snapshot(true);
2346
2347 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
2348 let mut rs = vec![0usize; num_threads];
2349 for j in 0..3 {
2350 let handles: Vec<_> = (0..num_threads)
2351 .map(|i| {
2352 let bftree_clone = bftree.clone();
2353 let start_id = j * inserts_per_thread;
2356 let end_id = start_id + inserts_per_thread;
2357 thread::spawn(move || {
2358 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
2359 assert!(key_len * 2 <= max_record_size);
2360 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
2361
2362 for r in start_id..end_id {
2363 key_buffer.fill(r);
2364 key_buffer[0] = i;
2365
2366 match bftree_clone.insert(
2367 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2368 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2369 ) {
2370 crate::LeafInsertResult::Success => {}
2371 _ => {
2372 panic!("Insert failed");
2373 }
2374 }
2375 }
2376 inserts_per_thread
2377 })
2378 })
2379 .collect();
2380
2381 let bftree_for_snap = bftree.clone();
2383 let snap_path = tmp_snapshot_file_path.clone();
2384 let snap_handle = thread::spawn(move || {
2385 bftree_for_snap.cpr_snapshot(&snap_path);
2386 });
2387
2388 for (i, h) in handles.into_iter().enumerate() {
2389 let r = h.join().unwrap();
2390 rs[i] += r;
2391 }
2392
2393 snap_handle.join().unwrap();
2394
2395 verify_snapshot_recovery(
2397 &tmp_snapshot_file_path,
2398 num_threads,
2399 min_record_size,
2400 &rs,
2401 true,
2402 );
2403 }
2404 let _ = std::fs::remove_file(tmp_file_path);
2405 let _ = std::fs::remove_file(tmp_snapshot_file_path);
2406 }
2407
2408 #[cfg(feature = "shuttle")]
2409 #[test]
2410 fn shuttle_cpr_snapshot_disk() {
2411 use std::sync::atomic::AtomicUsize;
2412
2413 static ITER: AtomicUsize = AtomicUsize::new(0);
2416
2417 let mut shuttle_config = shuttle::Config::default();
2418 shuttle_config.max_steps = shuttle::MaxSteps::None;
2419 shuttle_config.stack_size = 4 * 1024 * 1024; shuttle_config.failure_persistence =
2424 shuttle::FailurePersistence::File(Some(PathBuf::from_str("target").unwrap()));
2425
2426 let mut runner = shuttle::PortfolioRunner::new(true, shuttle_config);
2427 let available_cores = std::thread::available_parallelism().unwrap().get().min(4);
2428 for _ in 0..available_cores {
2429 runner.add(shuttle::scheduler::PctScheduler::new(10, 100));
2430 }
2431
2432 runner.run(|| {
2433 let iter = ITER.fetch_add(1, Ordering::Relaxed);
2434 shuttle_cpr_snapshot_disk_inner(iter);
2435 });
2436 }
2437
2438 #[cfg(feature = "shuttle")]
2439 #[test]
2440 fn shuttle_replay() {
2441 let schedule_path = "target/schedule000.txt";
2442 if !std::path::Path::new(schedule_path).exists() {
2443 eprintln!("No schedule file at {schedule_path}; run shuttle_cpr_snapshot_disk to generate one on failure.");
2444 return;
2445 }
2446
2447 tracing_subscriber::fmt()
2449 .with_ansi(true)
2450 .with_thread_names(false)
2451 .with_target(false)
2452 .init();
2453
2454 shuttle::replay_from_file(|| shuttle_cpr_snapshot_disk_inner(0), schedule_path);
2455 }
2456}