1use std::cell::UnsafeCell;
5use std::collections::{HashMap, VecDeque};
6use std::mem::{ManuallyDrop, MaybeUninit};
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 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
342 let local_state = self.thread_local_states[thread_slot_id].load(Ordering::Acquire);
343 if local_state != INVALID_SNAPSHOT_STATE && local_state != target_state {
344 return false;
345 }
346 }
347 true
348 }
349
350 fn wait_for_phase_completion(&self, target_state: u64) {
353 loop {
354 if self.check_if_phase_completed(target_state) {
356 return;
357 }
358
359 let waiter_val = self.phase_waiter.load(Ordering::Acquire);
361
362 if self.check_if_phase_completed(target_state) {
364 return;
365 }
366
367 atomic_wait::wait(&self.phase_waiter, waiter_val);
369
370 #[cfg(target_os = "macos")]
372 thread::sleep(std::time::Duration::from_millis(1));
373 }
374 }
375
376 fn wait_for_all_slots_released(&self) {
379 loop {
380 if self.check_if_phase_completed(INVALID_SNAPSHOT_STATE) {
382 return;
383 }
384
385 let waiter_val = self.phase_waiter.load(Ordering::Acquire);
387
388 if self.check_if_phase_completed(INVALID_SNAPSHOT_STATE) {
390 return;
391 }
392
393 atomic_wait::wait(&self.phase_waiter, waiter_val);
395
396 #[cfg(target_os = "macos")]
398 thread::sleep(std::time::Duration::from_millis(1));
399 }
400 }
401
402 pub fn reserve_thread_slot(&self) -> Result<(usize, u64, PhaseId), ()> {
406 if self.pause_snapshot.load(Ordering::Acquire) {
407 return Err(());
408 }
409
410 let start = get_rng().random_range(0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM);
411 let end = 2 * DEFAULT_MAX_SNAPSHOT_THREAD_NUM;
412
413 for i in start..end {
414 let tid = i % DEFAULT_MAX_SNAPSHOT_THREAD_NUM;
415 if self.thread_slots[tid]
416 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
417 .is_ok()
418 {
419 let global_state = self.global_state.load(Ordering::Acquire);
421 self.set_local_state(&tid, global_state);
422
423 let current_global = self.global_state.load(Ordering::Acquire);
433 if self.get_local_state(&tid) != current_global
434 || self.pause_snapshot.load(Ordering::Acquire)
435 {
436 self.set_local_state(&tid, INVALID_SNAPSHOT_STATE);
437 assert!(self.thread_slots[tid]
438 .compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed)
439 .is_ok());
440
441 self.phase_waiter.fetch_add(1, Ordering::Release);
443 atomic_wait::wake_all(&self.phase_waiter);
444
445 return Err(());
446 } else {
447 let version = global_state & SNAPSHOT_STATE_VERSION_MASK;
448 let phase_id = PhaseId::from_raw(
449 (global_state & SNAPSHOT_STATE_PHASE_ID_MASK)
450 >> SNAPSHOT_STATE_PHASE_ID_SHIFT,
451 );
452 return Ok((tid, version, phase_id));
453 }
454 }
455 }
456 Err(())
457 }
458
459 pub fn release_thread_slot(&self, thread_slot_id: usize) {
462 self.set_local_state(&thread_slot_id, INVALID_SNAPSHOT_STATE);
464 self.thread_slots[thread_slot_id].store(false, Ordering::Release);
465
466 self.phase_waiter.fetch_add(1, Ordering::Release);
469 atomic_wait::wake_all(&self.phase_waiter);
470 }
471
472 pub fn get_snapshot_guard(
473 snapshot_mgr: Option<Arc<CPRSnapShotMgr>>,
474 ) -> Result<CPRSnapshotGuard, ()> {
475 CPRSnapshotGuard::new(snapshot_mgr)
476 }
477
478 pub fn snapshot_page(&self, ptr: &[u8], size: usize) -> usize {
484 let vfs = self.vfs.read().unwrap().clone();
486 let offset = vfs.alloc_offset(size);
487
488 vfs.write(offset, ptr);
490
491 offset
493 }
494
495 pub fn snapshot_inner_node(&self, ptr: *const InnerNode, thread_slot_id: usize) {
496 let offset = unsafe { self.snapshot_page((&*ptr).as_slice(), INNER_NODE_SIZE) };
497 let inner_mappings = unsafe { &mut *self.thread_local_inner_mappings.get() };
498
499 inner_mappings[thread_slot_id].push((ptr, offset));
500 }
501
502 pub fn snapshot_mini_page(&self, id: PageID, ptr: &[u8], size: usize, thread_slot_id: usize) {
503 let offset = if size != 0 {
504 self.snapshot_page(ptr, size)
505 } else {
506 NULL_PAGE_LOCATION_OFFSET
508 };
509
510 let mini_mappings = unsafe { &mut *self.thread_local_mini_mappings.get() };
511 mini_mappings[thread_slot_id].push((id, offset));
512
513 let mini_size_mappings = unsafe { &mut *self.thread_local_mini_size_mappings.get() };
514 mini_size_mappings[thread_slot_id].push((id, size));
515
516 assert!(mini_mappings[thread_slot_id].len() == mini_size_mappings[thread_slot_id].len());
517 }
518
519 pub fn snapshot_base_page(&self, id: PageID, ptr: &[u8], size: usize, thread_slot_id: usize) {
520 let offset = self.snapshot_page(ptr, size);
521
522 let base_mappings = unsafe { &mut *self.thread_local_base_mappings.get() };
523 base_mappings[thread_slot_id].push((id, offset));
524 }
525
526 pub fn snapshot_root_page(&self, root_id: PageID) {
527 let cur_root_id = self.root_id.load(Ordering::Acquire);
528 if cur_root_id != 0 {
529 assert_eq!(cur_root_id, root_id.raw());
530 }
531
532 self.root_id.store(root_id.raw(), Ordering::Release);
533 }
534
535 fn sweep(
538 &self,
539 tree: &BfTree,
540 version: u64,
541 inner_mapping: &mut Vec<(*const InnerNode, usize)>,
542 mini_mapping: &mut Vec<(PageID, usize)>,
543 mini_size_mapping: &mut Vec<(PageID, usize)>,
544 base_mapping: &mut Vec<(PageID, usize)>,
545 ) -> usize {
546 self.pause_snapshot.store(true, Ordering::Release);
557 loop {
558 if self.check_if_phase_completed(INVALID_SNAPSHOT_STATE) {
559 loop {
562 let root_id = tree.get_root_page();
563 let rid = root_id.0;
564 if root_id.1 {
565 let mut leaf = tree.mapping_table().get(&rid);
567 let page_loc = leaf.get_page_location();
568
569 match page_loc {
570 PageLocation::Base(offset) => {
571 let base_ref = leaf.load_base_page(*offset);
572 if base_ref.get_clean_snapshot_version() < version {
573 let base_ptr = unsafe {
574 std::slice::from_raw_parts(
575 base_ref as *const LeafNode as *const u8,
576 base_ref.meta.node_size as usize,
577 )
578 };
579 let offset = self
580 .snapshot_page(base_ptr, base_ref.meta.node_size as usize);
581 base_mapping.push((rid, offset));
582 self.snapshot_root_page(rid);
583 }
584 }
585 PageLocation::Mini(ptr) => {
586 assert!(tree.cache_only);
588
589 let mini_ref = leaf.load_cache_page(*ptr);
590 if mini_ref.get_clean_snapshot_version() < version {
591 let mini_ptr = unsafe {
592 std::slice::from_raw_parts(
593 mini_ref as *const LeafNode as *const u8,
594 mini_ref.meta.node_size as usize,
595 )
596 };
597 let offset = self
598 .snapshot_page(mini_ptr, mini_ref.meta.node_size as usize);
599 mini_mapping.push((rid, offset));
600 mini_size_mapping.push((rid, mini_ref.meta.node_size as usize));
601 self.snapshot_root_page(rid);
602 }
603 }
604 _ => {
605 panic!("Unexpected page location for root page: {:?}", page_loc);
606 }
607 }
608
609 break;
610 } else {
611 let ptr = rid.as_inner_node();
613 let inner = match ReadGuard::try_read(ptr) {
615 Ok(inner) => inner,
616 Err(_) => continue,
617 };
618
619 if inner.as_ref().get_clean_snapshot_version() < version {
620 let offset =
621 unsafe { self.snapshot_page((&*ptr).as_slice(), INNER_NODE_SIZE) };
622 inner_mapping.push((ptr, offset));
623 self.snapshot_root_page(rid);
624 }
625 break;
626 }
627 }
628
629 let visitor = BfsVisitor::new_inner_only(tree);
631 for node in visitor {
632 loop {
633 match node {
634 NodeInfo::Inner { ptr, .. } => {
635 let inner = match ReadGuard::try_read(ptr) {
637 Ok(inner) => inner,
638 Err(_) => continue,
639 };
640
641 if inner.as_ref().get_clean_snapshot_version() < version {
642 let offset = unsafe {
643 self.snapshot_page((&*ptr).as_slice(), INNER_NODE_SIZE)
644 };
645 inner_mapping.push((ptr, offset));
646 }
647
648 break;
649 }
650 NodeInfo::Leaf { level, .. } => {
651 assert_eq!(level, 0);
653 break;
654 }
655 }
656 }
657 }
658
659 break;
660 }
661 self.wait_for_all_slots_released();
663
664 #[cfg(all(feature = "shuttle", test))]
665 shuttle::thread::yield_now();
666 }
667
668 self.pause_snapshot.store(false, Ordering::Release);
670
671 let page_table_iter = tree.storage.page_table.iter();
674 let mut enumerate_leaf_count = 0;
675
676 for (_, pid) in page_table_iter {
677 assert!(pid.is_id());
678
679 let mut leaf = tree.mapping_table().get(&pid);
681 let page_loc = leaf.get_page_location().clone();
682 enumerate_leaf_count += 1;
683
684 match page_loc {
685 PageLocation::Base(offset) => {
686 let base_ref = leaf.load_base_page(offset);
687 if base_ref.get_clean_snapshot_version() < version {
688 let base_ptr = unsafe {
689 std::slice::from_raw_parts(
690 base_ref as *const LeafNode as *const u8,
691 base_ref.meta.node_size as usize,
692 )
693 };
694 let new_offset =
695 self.snapshot_page(base_ptr, base_ref.meta.node_size as usize);
696 base_mapping.push((pid, new_offset));
697 }
698 }
699 PageLocation::Full(ptr) => {
700 let full_ref = leaf.load_cache_page(ptr);
702 if full_ref.get_clean_snapshot_version() < version {
703 let next_level = full_ref.next_level;
706 let full_page = unsafe { &mut *ptr };
707 full_page.next_level = MiniPageNextLevel::new_null();
708 let full_ptr = unsafe {
709 std::slice::from_raw_parts(
710 full_ref as *const LeafNode as *const u8,
711 full_ref.meta.node_size as usize,
712 )
713 };
714 let offset = self.snapshot_page(full_ptr, full_ref.meta.node_size as usize);
715 full_page.next_level = next_level;
716 base_mapping.push((pid, offset));
717 }
718 }
719 PageLocation::Mini(ptr) => {
720 let mini_ref = leaf.load_cache_page(ptr);
721 if mini_ref.get_clean_snapshot_version() < version {
722 let mini_ptr = unsafe {
723 std::slice::from_raw_parts(
724 mini_ref as *const LeafNode as *const u8,
725 mini_ref.meta.node_size as usize,
726 )
727 };
728 let offset = self.snapshot_page(mini_ptr, mini_ref.meta.node_size as usize);
729 mini_mapping.push((pid, offset));
730 mini_size_mapping.push((pid, mini_ref.meta.node_size as usize));
731
732 if !tree.cache_only {
733 let base_ref = leaf.load_base_page(mini_ref.next_level.as_offset());
735 assert!(base_ref.get_clean_snapshot_version() < version); let base_ptr = unsafe {
738 std::slice::from_raw_parts(
739 base_ref as *const LeafNode as *const u8,
740 base_ref.meta.node_size as usize,
741 )
742 };
743 let offset =
744 self.snapshot_page(base_ptr, base_ref.meta.node_size as usize);
745 base_mapping.push((pid, offset));
746 }
747 }
748 }
749 PageLocation::Null => {
750 assert!(tree.cache_only);
751 mini_mapping.push((pid, NULL_PAGE_LOCATION_OFFSET)); mini_size_mapping.push((pid, 0));
758 }
759 }
760 }
761
762 enumerate_leaf_count
763 }
764
765 #[allow(clippy::too_many_arguments)]
767 fn finalize(
768 &self,
769 snapshot_version: u64,
770 inner_mapping: &mut [(*const InnerNode, usize)],
771 mini_mapping: &mut [(PageID, usize)],
772 mini_size_mapping: &mut [(PageID, usize)],
773 base_mapping: &mut [(PageID, usize)],
774 leaf_count_upper: usize,
775 config: Arc<Config>,
776 ) {
777 let mut inner_mapping_unique: HashMap<*const InnerNode, usize> = HashMap::new();
779 let mut mini_mapping_unique: HashMap<PageID, usize> = HashMap::new();
780 let mut mini_size_mapping_unique: HashMap<PageID, usize> = HashMap::new();
781 let mut base_mapping_unique: HashMap<PageID, usize> = HashMap::new();
782
783 let local_inner_mappings = unsafe { &mut *self.thread_local_inner_mappings.get() };
784 let local_mini_mappings = unsafe { &mut *self.thread_local_mini_mappings.get() };
785 let local_mini_size_mappings = unsafe { &mut *self.thread_local_mini_size_mappings.get() };
786 let local_base_mappings = unsafe { &mut *self.thread_local_base_mappings.get() };
787
788 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
789 let entry_num = local_mini_mappings[thread_slot_id].len();
790 assert!(
791 local_mini_mappings[thread_slot_id].len()
792 == local_mini_size_mappings[thread_slot_id].len()
793 );
794 for i in 0..entry_num {
795 assert!(
796 local_mini_mappings[thread_slot_id][i].0
797 == local_mini_size_mappings[thread_slot_id][i].0
798 );
799
800 if local_mini_mappings[thread_slot_id][i].1 == NULL_PAGE_LOCATION_OFFSET {
801 assert_eq!(local_mini_size_mappings[thread_slot_id][i].1, 0);
802 } else {
803 assert!(local_mini_size_mappings[thread_slot_id][i].1 > 0);
804 }
805
806 if let std::collections::hash_map::Entry::Vacant(e) =
807 mini_mapping_unique.entry(local_mini_mappings[thread_slot_id][i].0)
808 {
809 e.insert(local_mini_mappings[thread_slot_id][i].1);
810 mini_size_mapping_unique.insert(
811 local_mini_size_mappings[thread_slot_id][i].0,
812 local_mini_size_mappings[thread_slot_id][i].1,
813 );
814 assert!(mini_mapping_unique.len() == mini_size_mapping_unique.len());
815 }
816 }
817 assert_eq!(local_mini_mappings[thread_slot_id].len(), entry_num);
818 assert_eq!(local_mini_size_mappings[thread_slot_id].len(), entry_num);
819 }
820
821 for thread_slot_id in 0..DEFAULT_MAX_SNAPSHOT_THREAD_NUM {
823 for m in local_inner_mappings[thread_slot_id].iter() {
824 inner_mapping_unique.entry(m.0).or_insert(m.1);
825 }
826
827 for m in local_base_mappings[thread_slot_id].iter() {
828 base_mapping_unique.entry(m.0).or_insert(m.1);
829 }
830 }
831
832 assert!(mini_mapping_unique.len() == mini_size_mapping_unique.len());
834 for (k, v) in mini_mapping_unique.iter() {
835 assert!(mini_size_mapping_unique.contains_key(k));
836 if *v == NULL_PAGE_LOCATION_OFFSET {
837 assert_eq!(mini_size_mapping_unique.get(k).copied().unwrap(), 0);
838 } else {
839 assert!(mini_size_mapping_unique.get(k).copied().unwrap() > 0);
840 }
841 }
842
843 for (k, v) in inner_mapping.iter() {
845 if !inner_mapping_unique.contains_key(k) {
846 inner_mapping_unique.insert(*k, *v);
847 }
848 }
849 for (k, v) in mini_mapping.iter() {
850 if !mini_mapping_unique.contains_key(k) {
851 mini_mapping_unique.insert(*k, *v);
852 }
853 }
854 for (k, v) in mini_size_mapping.iter() {
855 if !mini_size_mapping_unique.contains_key(k) {
856 mini_size_mapping_unique.insert(*k, *v);
857 } else {
858 if !config.cache_only {
859 assert!(*v == mini_size_mapping_unique.get(k).copied().unwrap());
860 }
861 }
862 }
863 for (k, v) in base_mapping.iter() {
864 if !base_mapping_unique.contains_key(k) {
865 base_mapping_unique.insert(*k, *v);
866 }
867 }
868
869 assert!(mini_mapping_unique.len() == mini_size_mapping_unique.len());
871 for (k, v) in mini_mapping_unique.iter() {
872 assert!(mini_size_mapping_unique.contains_key(k));
873 if *v == NULL_PAGE_LOCATION_OFFSET {
874 assert_eq!(mini_size_mapping_unique.get(k).copied().unwrap(), 0);
875 } else {
876 assert!(mini_size_mapping_unique.get(k).copied().unwrap() > 0);
877 }
878 }
879
880 if config.cache_only {
881 assert!(base_mapping_unique.is_empty());
882 } else {
883 assert!(mini_mapping_unique.len() <= base_mapping_unique.len());
884 }
885
886 let mut final_inner_mapping: Vec<(*const InnerNode, usize)> = Vec::new();
888 for (k, v) in inner_mapping_unique.into_iter() {
889 final_inner_mapping.push((k, v));
890 }
891
892 let mut sorted_base_mapping_uninit: Vec<MaybeUninit<(PageID, usize)>> =
896 Vec::with_capacity(base_mapping_unique.len());
897 unsafe {
898 sorted_base_mapping_uninit.set_len(base_mapping_unique.len());
899 }
900 let mut sorted_base_mapping_init = vec![false; base_mapping_unique.len()];
901
902 for (k, v) in base_mapping_unique.iter() {
903 assert!(k.is_id());
904 let offset = k.as_id();
905 assert!((offset as usize) < sorted_base_mapping_uninit.len());
906 sorted_base_mapping_init[offset as usize] = true;
907 sorted_base_mapping_uninit[offset as usize].write((*k, *v));
908 }
909 let final_sorted_base_mapping: Vec<(PageID, usize)> = if !config.cache_only {
910 assert_eq!(
911 base_mapping_unique.len(),
912 sorted_base_mapping_init.iter().filter(|&&b| b).count()
913 );
914 unsafe {
915 std::mem::transmute::<
916 std::vec::Vec<std::mem::MaybeUninit<(PageID, usize)>>,
917 std::vec::Vec<(PageID, usize)>,
918 >(sorted_base_mapping_uninit)
919 }
920 } else {
921 Vec::new()
922 };
923
924 let mut final_mini_mapping: Vec<(PageID, usize)> = Vec::new();
926 for (k, v) in mini_mapping_unique.into_iter() {
927 final_mini_mapping.push((k, v));
928 }
929
930 let mut final_mini_size_mapping: Vec<(PageID, usize)> = Vec::new();
931 for (k, v) in mini_size_mapping_unique.into_iter() {
932 final_mini_size_mapping.push((k, v));
933 }
934
935 let leaf_page_num = if config.cache_only {
936 leaf_count_upper
944 } else {
945 assert!(leaf_count_upper >= final_sorted_base_mapping.len());
946 final_sorted_base_mapping.len()
947 };
948
949 let mut file_size = std::mem::size_of::<BfTreeMeta>() as u64;
950
951 let (inner_offset, inner_size) =
953 serialize_vec_to_disk(&final_inner_mapping, &self.vfs.read().unwrap());
954
955 if inner_offset != 0 {
956 file_size = (inner_offset + align_to_sector_size(inner_size)) as u64;
957 }
958
959 let (mini_offset, mini_size) =
960 serialize_vec_to_disk(&final_mini_mapping, &self.vfs.read().unwrap());
961
962 if mini_offset != 0 {
963 file_size = (mini_offset + align_to_sector_size(mini_size)) as u64;
964 }
965
966 let (mini_size_offset, mini_size_size) =
967 serialize_vec_to_disk(&final_mini_size_mapping, &self.vfs.read().unwrap());
968
969 if mini_size_offset != 0 {
970 file_size = (mini_size_offset + align_to_sector_size(mini_size_size)) as u64;
971 }
972
973 let (base_offset, base_size) =
974 serialize_vec_to_disk(&final_sorted_base_mapping, &self.vfs.read().unwrap());
975
976 if base_offset != 0 {
977 file_size = (base_offset + align_to_sector_size(base_size)) as u64;
978 }
979
980 let metadata = BfTreeMeta {
982 magic_begin: *BF_TREE_MAGIC_BEGIN,
983 root_id: unsafe { PageID::from_raw(self.root_id.load(Ordering::Acquire)) },
984 inner_offset,
985 inner_size,
986 mini_offset,
987 mini_size,
988 mini_size_offset,
989 mini_size_size,
990 base_offset,
991 base_size,
992 file_size,
993 leaf_page_num,
994 snapshot_version,
995 cache_only: config.cache_only,
996 cb_size_byte: config.cb_size_byte,
997 read_promotion_rate: config.read_promotion_rate.load(Ordering::Relaxed),
998 scan_promotion_rate: config.scan_promotion_rate.load(Ordering::Relaxed),
999 cb_min_record_size: config.cb_min_record_size,
1000 cb_max_record_size: config.cb_max_record_size,
1001 leaf_page_size: config.leaf_page_size,
1002 cb_max_key_len: config.cb_max_key_len,
1003 max_fence_len: config.max_fence_len,
1004 cb_copy_on_access_ratio: config.cb_copy_on_access_ratio,
1005 read_record_cache: config.read_record_cache,
1006 max_mini_page_size: config.max_mini_page_size,
1007 mini_page_binary_search: config.mini_page_binary_search,
1008 write_load_full_page: config.write_load_full_page,
1009 magic_end: *BF_TREE_MAGIC_END,
1010 };
1011
1012 let vfs = self.vfs.read().unwrap();
1013 vfs.write(META_DATA_PAGE_OFFSET, metadata.as_slice());
1014 vfs.flush();
1015
1016 self.reset();
1017 }
1018
1019 pub fn snapshot(&self, tree: &BfTree, snapshot_file_path: impl AsRef<Path>) {
1021 if self
1023 .snapshot_in_progress
1024 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
1025 .is_err()
1026 {
1027 println!("Another snapshot is in progress, skipping this snapshot request.");
1028 return;
1029 }
1030
1031 let mut vfs_guard = self.vfs.write().unwrap();
1033 let snapshot_vfs = make_vfs(&tree.config.snapshot_backend, snapshot_file_path);
1034 let old_vfs = std::mem::replace(&mut *vfs_guard, snapshot_vfs);
1035 drop(old_vfs);
1036
1037 vfs_guard.reset();
1039
1040 drop(vfs_guard);
1042
1043 let mut sweep_inner_mapping: Vec<(*const InnerNode, usize)> = Vec::new();
1045 let mut sweep_mini_mapping: Vec<(PageID, usize)> = Vec::new();
1046 let mut sweep_mini_size_mapping: Vec<(PageID, usize)> = Vec::new();
1047 let mut sweep_base_mapping: Vec<(PageID, usize)> = Vec::new();
1048
1049 let mut current_global_phase_id = self.get_global_phase_id();
1051 assert_eq!(current_global_phase_id, PhaseId::Rest);
1052
1053 let snapshot_version = self.get_global_version();
1055
1056 let mut current_global_state = self.advance_global_state();
1058 current_global_phase_id = self.get_global_phase_id();
1059 assert_eq!(current_global_phase_id, PhaseId::Prepare);
1060 assert_eq!(snapshot_version, self.get_global_version());
1061
1062 let mut leaf_node_count_upper_bound = 0; loop {
1065 if self.check_if_phase_completed(current_global_state) {
1066 match current_global_phase_id {
1067 PhaseId::Rest => {
1068 self.finalize(
1073 snapshot_version,
1074 &mut sweep_inner_mapping,
1075 &mut sweep_mini_mapping,
1076 &mut sweep_mini_size_mapping,
1077 &mut sweep_base_mapping,
1078 leaf_node_count_upper_bound,
1079 tree.config.clone(),
1080 );
1081
1082 let mut vfs_guard = self.vfs.write().unwrap();
1084 let snapshot_vfs = make_vfs(&StorageBackend::Memory, ":memory:");
1085 let old_vfs = std::mem::replace(&mut *vfs_guard, snapshot_vfs);
1086 drop(old_vfs);
1087 drop(vfs_guard);
1088
1089 break;
1091 }
1092 PhaseId::Prepare => {
1093 current_global_state = self.advance_global_state();
1094 current_global_phase_id = self.get_global_phase_id();
1095 assert_eq!(current_global_phase_id, PhaseId::InProgress);
1096 assert_eq!(snapshot_version + 1, self.get_global_version());
1097 }
1098 PhaseId::InProgress => {
1099 current_global_state = self.advance_global_state();
1100 current_global_phase_id = self.get_global_phase_id();
1101 assert_eq!(current_global_phase_id, PhaseId::Sweep);
1102 assert_eq!(snapshot_version + 1, self.get_global_version());
1103 }
1104 PhaseId::Sweep => {
1105 leaf_node_count_upper_bound = self.sweep(
1113 tree,
1114 snapshot_version + 1,
1115 &mut sweep_inner_mapping,
1116 &mut sweep_mini_mapping,
1117 &mut sweep_mini_size_mapping,
1118 &mut sweep_base_mapping,
1119 );
1120
1121 current_global_state = self.advance_global_state();
1122 current_global_phase_id = self.get_global_phase_id();
1123 assert_eq!(current_global_phase_id, PhaseId::Rest);
1124 assert_eq!(snapshot_version + 1, self.get_global_version());
1125 }
1126 }
1127 }
1128
1129 self.wait_for_phase_completion(current_global_state);
1132
1133 #[cfg(all(feature = "shuttle", test))]
1134 shuttle::thread::yield_now();
1135 }
1136
1137 assert!(self
1138 .snapshot_in_progress
1139 .compare_exchange(true, false, Ordering::AcqRel, Ordering::Relaxed)
1140 .is_ok());
1141 }
1142
1143 pub fn new_from_snapshot(
1157 recovery_snapshot_file_path: impl AsRef<Path>, use_snapshot: bool,
1159 buffer_ptr: Option<*mut u8>,
1160 buffer_size: Option<usize>, wal_config: Option<Arc<WalConfig>>,
1162 ) -> Result<BfTree, ConfigError> {
1163 if !recovery_snapshot_file_path.as_ref().exists() {
1165 return Err(ConfigError::SnapshotFileInvalid(
1167 "Not found ".to_string() + recovery_snapshot_file_path.as_ref().to_str().unwrap(),
1168 ));
1169 }
1170
1171 let wal = wal_config.as_ref().map(|s| WriteAheadLog::new(s.clone()));
1173
1174 let reader = std::fs::File::open(recovery_snapshot_file_path.as_ref()).unwrap();
1176 let mut metadata = SectorAlignedVector::new_zeroed(DISK_PAGE_SIZE); #[cfg(unix)]
1178 {
1179 reader.read_at(&mut metadata, 0).unwrap();
1180 }
1181 #[cfg(windows)]
1182 {
1183 reader.seek_read(&mut metadata, 0).unwrap();
1184 }
1185
1186 let bf_meta = unsafe { (metadata.as_ptr() as *const BfTreeMeta).read() };
1187 bf_meta.check_magic();
1188 assert_eq!(reader.metadata().unwrap().len(), bf_meta.file_size);
1189
1190 let mut bf_tree_config = Config::new_from_snapshot(&bf_meta);
1191
1192 let recovery_snapshot_file_backend = StorageBackend::Std; if !bf_tree_config.cache_only {
1194 bf_tree_config.file_path(recovery_snapshot_file_path.as_ref());
1195 bf_tree_config.storage_backend = recovery_snapshot_file_backend.clone();
1197 } else {
1198 bf_tree_config.storage_backend = StorageBackend::Memory;
1199 }
1200 bf_tree_config.use_snapshot = use_snapshot;
1201
1202 bf_tree_config.snapshot_backend = StorageBackend::Std; let snapshot_mgr = if bf_tree_config.use_snapshot {
1205 Some(Arc::new(CPRSnapShotMgr::new(
1206 bf_tree_config.snapshot_version,
1207 )))
1208 } else {
1209 None
1210 };
1211
1212 if let Some(size) = buffer_size {
1213 bf_tree_config.cb_size_byte = size
1214 }
1215
1216 let size_classes = BfTree::create_mem_page_size_classes(
1217 bf_tree_config.cb_min_record_size,
1218 bf_tree_config.cb_max_record_size,
1219 bf_tree_config.leaf_page_size,
1220 bf_tree_config.max_fence_len,
1221 bf_tree_config.cache_only,
1222 );
1223
1224 bf_tree_config.write_ahead_log = wal_config.clone();
1225 bf_tree_config.validate()?;
1226
1227 let config = Arc::new(bf_tree_config);
1228
1229 let recovery_snapshot_vfs = make_vfs(
1231 &recovery_snapshot_file_backend,
1232 recovery_snapshot_file_path.as_ref(),
1233 );
1234
1235 let mut root_page_id = bf_meta.root_id;
1237 let mut inner_node_page_buffer = SectorAlignedVector::new_zeroed(INNER_NODE_SIZE);
1238 if root_page_id.is_inner_node_pointer() {
1239 let inner_mapping: Vec<(*const InnerNode, usize)> = read_vec_from_offset(
1240 bf_meta.inner_offset,
1241 bf_meta.inner_size,
1242 &recovery_snapshot_vfs,
1243 );
1244
1245 let mut root_cnt = 0;
1247 for (_ptr, offset) in &inner_mapping {
1248 recovery_snapshot_vfs.read(*offset, &mut inner_node_page_buffer);
1249 let inner_node = InnerNodeBuilder::new().build_from_slice(&inner_node_page_buffer);
1250 if unsafe { (*inner_node).is_root() } {
1251 root_cnt += 1;
1252 }
1253 InnerNode::free_node(inner_node);
1254 }
1255 assert_eq!(root_cnt, 1, "Root count in inner mapping: {}", root_cnt);
1256
1257 let mut inner_map = HashMap::new();
1258
1259 for m in inner_mapping {
1260 inner_map.insert(m.0, m.1);
1261 }
1262 let offset = inner_map.get(&root_page_id.as_inner_node()).unwrap();
1263 recovery_snapshot_vfs.read(*offset, &mut inner_node_page_buffer);
1264 let root_page = InnerNodeBuilder::new().build_from_slice(&inner_node_page_buffer);
1265
1266 unsafe {
1268 (*root_page).set_disk_offset(INVALID_DISK_OFFSET as u64);
1269 }
1270 unsafe {
1272 (*root_page).set_root(true);
1273 }
1274 root_page_id = PageID::from_pointer(root_page);
1275
1276 let mut inner_resolve_queue = VecDeque::from([root_page]);
1277 while !inner_resolve_queue.is_empty() {
1278 let inner_ptr = inner_resolve_queue.pop_front().unwrap();
1279 let mut inner = ReadGuard::try_read(inner_ptr).unwrap().upgrade().unwrap();
1280 if inner.as_ref().meta.children_is_leaf() {
1281 continue;
1282 }
1283 for (idx, c) in inner.as_ref().get_child_iter().enumerate() {
1284 let offset = inner_map.get(&c.as_inner_node()).unwrap();
1285 recovery_snapshot_vfs.read(*offset, &mut inner_node_page_buffer);
1286 let inner_page =
1287 InnerNodeBuilder::new().build_from_slice(&inner_node_page_buffer);
1288 unsafe {
1289 (*inner_page).set_disk_offset(INVALID_DISK_OFFSET as u64);
1290 }
1291 let inner_id = PageID::from_pointer(inner_page);
1292 inner.as_mut().update_at_pos(idx, inner_id);
1293 inner_resolve_queue.push_back(inner_page);
1294 }
1295 }
1296 }
1297
1298 let raw_root_id = if root_page_id.is_id() {
1299 root_page_id.raw() | BfTree::ROOT_IS_LEAF_MASK
1300 } else {
1301 root_page_id.raw()
1302 };
1303
1304 if !bf_meta.cache_only {
1307 let base_mapping: Vec<(PageID, usize)> = if bf_meta.base_size > 0 {
1309 read_vec_from_offset(
1310 bf_meta.base_offset,
1311 bf_meta.base_size,
1312 &recovery_snapshot_vfs,
1313 )
1314 } else {
1315 Vec::new()
1316 };
1317
1318 let base_page_loc_mapping = base_mapping.into_iter().map(|(pid, offset)| {
1319 let loc = PageLocation::Base(offset);
1320 (pid, loc)
1321 });
1322
1323 let pt = PageTable::new_from_mapping(
1325 base_page_loc_mapping,
1326 recovery_snapshot_vfs.clone(),
1327 config.clone(),
1328 snapshot_mgr.clone(),
1329 );
1330
1331 let circular_buffer = CircularBuffer::new(
1332 config.cb_size_byte,
1333 config.cb_copy_on_access_ratio,
1334 config.cb_min_record_size,
1335 config.cb_max_record_size,
1336 config.leaf_page_size,
1337 config.max_fence_len,
1338 buffer_ptr,
1339 config.cache_only,
1340 );
1341
1342 let mini_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size > 0 {
1344 read_vec_from_offset(
1345 bf_meta.mini_offset,
1346 bf_meta.mini_size,
1347 &recovery_snapshot_vfs,
1348 )
1349 } else {
1350 Vec::new()
1351 };
1352
1353 let mini_size_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size_size > 0 {
1354 read_vec_from_offset(
1355 bf_meta.mini_size_offset,
1356 bf_meta.mini_size_size,
1357 &recovery_snapshot_vfs,
1358 )
1359 } else {
1360 Vec::new()
1361 };
1362
1363 let mut mini_size_mapping_unique: HashMap<PageID, usize> = HashMap::new();
1364 for (pid, size) in mini_size_mapping {
1365 mini_size_mapping_unique.insert(pid, size);
1366 }
1367
1368 let storage =
1370 LeafStorage::new_inner(config.clone(), pt, circular_buffer, recovery_snapshot_vfs);
1371
1372 for (pid, offset) in &mini_mapping {
1373 let mini_size = *mini_size_mapping_unique.get(pid).unwrap();
1374
1375 let mini_page_guard = match storage.alloc_mini_page(mini_size) {
1377 Ok(mini_page_ptr) => mini_page_ptr,
1378 Err(_) => {
1379 return Err(ConfigError::CircularBufferSize("buffer size set too small. Consider increasing it or not specifying at all".to_string()));
1380 }
1381 };
1382
1383 let mut page_buffer = SectorAlignedVector::new_zeroed(mini_size);
1385 storage.vfs.read(*offset, &mut page_buffer);
1386 unsafe {
1387 std::ptr::copy_nonoverlapping(
1388 page_buffer.as_ptr(),
1389 mini_page_guard.as_ptr(),
1390 mini_size,
1391 );
1392 }
1393
1394 let new_mini_ptr = mini_page_guard.as_ptr() as *mut LeafNode;
1396 let mini_page = unsafe { &mut *new_mini_ptr };
1397
1398 let mut base_page = storage.page_table.get_mut(pid);
1399 let page_loc = base_page.get_page_location().clone();
1400 match page_loc {
1401 PageLocation::Base(off) => {
1402 mini_page.next_level = MiniPageNextLevel::new(off);
1403 }
1404 _ => {
1405 panic!("Unexpected page location for base page");
1406 }
1407 }
1408 let mini_loc = PageLocation::Mini(new_mini_ptr);
1409
1410 base_page.create_cache_page_loc(mini_loc);
1412 }
1413
1414 Ok(BfTree {
1415 storage,
1416 root_page_id: AtomicU64::new(raw_root_id),
1417 wal,
1418 write_load_full_page: config.write_load_full_page,
1419 cache_only: false,
1420 mini_page_size_classes: size_classes,
1421 snapshot_mgr,
1422 config,
1423 #[cfg(any(feature = "metrics-rt-debug-all", feature = "metrics-rt-debug-timer"))]
1424 metrics_recorder: Some(Arc::new(ThreadLocal::new())),
1425 })
1426 } else {
1427 let mini_mapping_unallocated: Vec<(PageID, PageLocation)> = (0..bf_meta.leaf_page_num)
1429 .map(|pid| (PageID::from_id(pid as u64), PageLocation::Null))
1430 .collect();
1431
1432 let mini_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size > 0 {
1434 read_vec_from_offset(
1435 bf_meta.mini_offset,
1436 bf_meta.mini_size,
1437 &recovery_snapshot_vfs,
1438 )
1439 } else {
1440 Vec::new()
1441 };
1442
1443 let mini_size_mapping: Vec<(PageID, usize)> = if bf_meta.mini_size_size > 0 {
1444 read_vec_from_offset(
1445 bf_meta.mini_size_offset,
1446 bf_meta.mini_size_size,
1447 &recovery_snapshot_vfs,
1448 )
1449 } else {
1450 Vec::new()
1451 };
1452
1453 let mut mini_size_mapping_unique: HashMap<PageID, usize> = HashMap::new();
1454 for (pid, size) in mini_size_mapping {
1455 mini_size_mapping_unique.insert(pid, size);
1456 }
1457
1458 let storage_vfs = make_vfs(&config.storage_backend, PathBuf::new());
1460
1461 let pt = PageTable::new_from_mapping(
1462 mini_mapping_unallocated.into_iter(),
1463 storage_vfs.clone(),
1464 config.clone(),
1465 snapshot_mgr.clone(),
1466 );
1467
1468 let circular_buffer = CircularBuffer::new(
1469 config.cb_size_byte,
1470 config.cb_copy_on_access_ratio,
1471 config.cb_min_record_size,
1472 config.cb_max_record_size,
1473 config.leaf_page_size,
1474 config.max_fence_len,
1475 buffer_ptr,
1476 config.cache_only,
1477 );
1478
1479 let storage = LeafStorage::new_inner(config.clone(), pt, circular_buffer, storage_vfs);
1481
1482 for (pid, offset) in &mini_mapping {
1483 let mini_size = *mini_size_mapping_unique.get(pid).unwrap();
1484
1485 if *offset == NULL_PAGE_LOCATION_OFFSET {
1487 continue;
1488 }
1489
1490 let mini_page_guard = match storage.alloc_mini_page(mini_size) {
1492 Ok(mini_page_ptr) => mini_page_ptr,
1493 Err(_) => {
1494 panic!("Please increase cb_size_byte in config");
1495 }
1496 };
1497
1498 let mut page_buffer = SectorAlignedVector::new_zeroed(mini_size);
1500 recovery_snapshot_vfs.read(*offset, &mut page_buffer);
1501 unsafe {
1502 std::ptr::copy_nonoverlapping(
1503 page_buffer.as_ptr(),
1504 mini_page_guard.as_ptr(),
1505 mini_size,
1506 );
1507 }
1508
1509 let mini_page_ptr = mini_page_guard.as_ptr() as *mut LeafNode;
1511 let mini_page = unsafe { &mut *mini_page_ptr };
1512 mini_page.next_level = MiniPageNextLevel::new_null();
1513
1514 let mut null_page = storage.page_table.get_mut(pid);
1516 let page_loc = null_page.get_page_location().clone();
1517 match page_loc {
1518 PageLocation::Null => {
1519 let mini_loc = PageLocation::Mini(mini_page_ptr);
1520 null_page.create_cache_page_loc(mini_loc);
1521 }
1522 _ => {
1523 panic!("Unexpected page location for null page");
1524 }
1525 }
1526 }
1527 Ok(BfTree {
1528 storage,
1529 root_page_id: AtomicU64::new(raw_root_id),
1530 wal,
1531 write_load_full_page: config.write_load_full_page,
1532 cache_only: true,
1533 mini_page_size_classes: size_classes,
1534 snapshot_mgr,
1535 config,
1536 #[cfg(any(feature = "metrics-rt-debug-all", feature = "metrics-rt-debug-timer"))]
1537 metrics_recorder: Some(Arc::new(ThreadLocal::new())),
1538 })
1539 }
1540 }
1541}
1542
1543impl BfTree {
1544 pub fn recovery(
1547 recovery_snapshot_file_path: PathBuf, wal_file: impl AsRef<Path>,
1549 use_snapshot: bool,
1550 buffer_ptr: Option<*mut u8>,
1551 buffer_size: Option<usize>,
1552 wal: Option<Arc<WalConfig>>,
1553 ) {
1554 let bf_tree = BfTree::new_from_cpr_snapshot(
1555 recovery_snapshot_file_path,
1556 use_snapshot,
1557 buffer_ptr,
1558 buffer_size,
1559 wal,
1560 )
1561 .unwrap();
1562 let wal_reader = WalReader::new(wal_file, 4096);
1563
1564 for seg in wal_reader.segment_iter() {
1565 for entry in seg.entry_iter() {
1566 let log_entry = LogEntry::read_from_buffer(entry.1);
1567 match log_entry {
1568 LogEntry::Write(op) => {
1569 bf_tree.insert(op.key, op.value);
1570 }
1571 LogEntry::Split(_op) => {
1572 todo!("implement split op in wal!")
1573 }
1574 }
1575 }
1576 }
1577 }
1578
1579 pub fn cpr_snapshot(&self, snapshot_file_path: impl AsRef<Path>) {
1581 if !self.config.use_snapshot {
1582 panic!("Snapshots are not enabled in the configuration");
1583 }
1584
1585 let snpshot_mgr = self.snapshot_mgr.clone().unwrap();
1586 snpshot_mgr.snapshot(self, snapshot_file_path);
1587 }
1588
1589 pub fn new_from_cpr_snapshot(
1591 recovery_snapshot_file_path: impl AsRef<Path>, use_snapshot: bool,
1593 buffer_ptr: Option<*mut u8>,
1594 buffer_size: Option<usize>,
1595 wal: Option<Arc<WalConfig>>,
1596 ) -> Result<BfTree, ConfigError> {
1597 CPRSnapShotMgr::new_from_snapshot(
1598 recovery_snapshot_file_path,
1599 use_snapshot,
1600 buffer_ptr,
1601 buffer_size,
1602 wal,
1603 )
1604 }
1605
1606 pub fn are_all_threads_in_next_snapshot_version(&self) -> bool {
1608 if let Some(snapshot_mgr) = &self.snapshot_mgr {
1609 return snapshot_mgr.are_all_threads_in_next_version();
1610 }
1611 false
1612 }
1613}
1614
1615struct SectorAlignedVector {
1616 inner: ManuallyDrop<Vec<u8>>,
1617}
1618
1619impl Drop for SectorAlignedVector {
1620 fn drop(&mut self) {
1621 let layout =
1622 std::alloc::Layout::from_size_align(self.inner.capacity(), SECTOR_SIZE).unwrap();
1623 let ptr = self.inner.as_mut_ptr();
1624 unsafe {
1625 std::alloc::dealloc(ptr, layout);
1626 }
1627 }
1628}
1629
1630impl SectorAlignedVector {
1631 fn new_zeroed(capacity: usize) -> Self {
1632 let layout = std::alloc::Layout::from_size_align(capacity, SECTOR_SIZE).unwrap();
1633 let ptr = unsafe { std::alloc::alloc_zeroed(layout) };
1634
1635 let inner = unsafe { Vec::from_raw_parts(ptr, capacity, capacity) };
1636 Self {
1637 inner: ManuallyDrop::new(inner),
1638 }
1639 }
1640}
1641
1642impl Deref for SectorAlignedVector {
1643 type Target = Vec<u8>;
1644
1645 fn deref(&self) -> &Self::Target {
1646 &self.inner
1647 }
1648}
1649
1650impl DerefMut for SectorAlignedVector {
1651 fn deref_mut(&mut self) -> &mut Self::Target {
1652 &mut self.inner
1653 }
1654}
1655
1656#[repr(C, align(512))]
1660pub(crate) struct BfTreeMeta {
1661 magic_begin: [u8; 16],
1662 root_id: PageID,
1664 inner_offset: usize,
1665 inner_size: usize,
1666 mini_offset: usize,
1667 mini_size: usize,
1668 mini_size_offset: usize,
1669 mini_size_size: usize,
1670 base_offset: usize,
1671 base_size: usize,
1672 file_size: u64,
1673 leaf_page_num: usize,
1674 pub(crate) cb_size_byte: usize,
1676 pub(crate) snapshot_version: u64,
1677 pub(crate) cache_only: bool,
1678 pub(crate) read_promotion_rate: usize,
1679 pub(crate) scan_promotion_rate: usize,
1680 pub(crate) cb_min_record_size: usize,
1681 pub(crate) cb_max_record_size: usize,
1682 pub(crate) leaf_page_size: usize,
1683 pub(crate) cb_max_key_len: usize,
1684 pub(crate) max_fence_len: usize,
1685 pub(crate) cb_copy_on_access_ratio: f64,
1686 pub(crate) read_record_cache: bool,
1687 pub(crate) max_mini_page_size: usize,
1688 pub(crate) mini_page_binary_search: bool,
1689 pub(crate) write_load_full_page: bool,
1690 magic_end: [u8; 14],
1691}
1692const _: () = assert!(std::mem::size_of::<BfTreeMeta>() <= DISK_PAGE_SIZE);
1693
1694impl BfTreeMeta {
1695 fn as_slice(&self) -> &[u8] {
1696 let ptr = self as *const Self as *const u8;
1697 let size = std::mem::size_of::<Self>();
1698 unsafe { std::slice::from_raw_parts(ptr, size) }
1699 }
1700
1701 fn check_magic(&self) {
1702 assert_eq!(self.magic_begin, *BF_TREE_MAGIC_BEGIN);
1703 assert_eq!(self.magic_end, *BF_TREE_MAGIC_END);
1704 }
1705}
1706
1707fn serialize_vec_to_disk<T>(v: &[T], vfs: &Arc<dyn VfsImpl>) -> (usize, usize) {
1709 if v.is_empty() {
1710 return (0, 0);
1711 }
1712 let unaligned_ptr = v.as_ptr() as *const u8;
1713 let unaligned_size = std::mem::size_of_val(v);
1714
1715 let aligned_size = align_to_sector_size(unaligned_size);
1716 let layout = std::alloc::Layout::from_size_align(aligned_size, SECTOR_SIZE).unwrap();
1717 unsafe {
1718 let aligned_ptr = std::alloc::alloc_zeroed(layout);
1719 std::ptr::copy_nonoverlapping(unaligned_ptr, aligned_ptr, unaligned_size);
1720 let slice = std::slice::from_raw_parts(aligned_ptr, aligned_size);
1721 let offset = serialize_u8_slice_to_disk(slice, vfs);
1722 std::alloc::dealloc(aligned_ptr, layout);
1723 (offset, unaligned_size)
1724 }
1725}
1726
1727fn read_vec_from_offset<T: Clone>(offset: usize, size: usize, vfs: &Arc<dyn VfsImpl>) -> Vec<T> {
1728 assert!(size > 0);
1729 let slice = read_u8_slice_from_disk(offset, size, vfs);
1730 let ptr = slice.as_ptr() as *const T;
1731 let size = size / std::mem::size_of::<T>();
1732 let slice = unsafe { std::slice::from_raw_parts(ptr, size) };
1733 slice.to_vec()
1734}
1735
1736fn read_u8_slice_from_disk(offset: usize, size: usize, vfs: &Arc<dyn VfsImpl>) -> Vec<u8> {
1737 let mut res = Vec::new();
1738 let mut buffer = vec![0; DISK_PAGE_SIZE];
1739 for i in (0..size).step_by(DISK_PAGE_SIZE) {
1740 vfs.read(offset + i, &mut buffer); res.extend_from_slice(&buffer);
1742 }
1743 res
1744}
1745
1746const SECTOR_SIZE: usize = 512;
1747
1748fn align_to_sector_size(n: usize) -> usize {
1749 (n + SECTOR_SIZE - 1) & !(SECTOR_SIZE - 1)
1750}
1751
1752fn serialize_u8_slice_to_disk(slice: &[u8], vfs: &Arc<dyn VfsImpl>) -> usize {
1756 let mut start_offset = None;
1757 for chunk in slice.chunks(DISK_PAGE_SIZE) {
1758 let offset = vfs.alloc_offset(DISK_PAGE_SIZE); if start_offset.is_none() {
1760 start_offset = Some(offset);
1761 }
1762 vfs.write(offset, chunk);
1763 }
1764 start_offset.unwrap()
1765}
1766
1767#[cfg(test)]
1768mod tests {
1769 use crate::{nodes::leaf_node::LeafReadResult, sync::thread, BfTree, Config};
1770 use std::panic;
1771 #[cfg(feature = "shuttle")]
1772 use std::path::PathBuf;
1773 use std::str::FromStr;
1774 use std::sync::atomic::Ordering;
1775 use std::sync::{atomic::AtomicBool, Arc};
1776
1777 #[test]
1781 fn cpr_snapshot_disk() {
1782 panic::set_hook(Box::new(|info| {
1785 eprintln!("PANIC: {info}");
1786 unsafe { std::arch::asm!("int 3") };
1787 }));
1788
1789 let min_record_size: usize = 64;
1790 let max_record_size: usize = 2408;
1791 let leaf_page_size: usize = 8192;
1792 let snapshot_num: usize = 10;
1793 let num_threads: usize = 4;
1794 let file_path: String = "target/test_simple.bftree".to_string();
1795 let snapshot_file_path: String = "target/test_simple_snapshot.bftree".to_string();
1796
1797 let tmp_file_path = std::path::PathBuf::from_str(&file_path).unwrap();
1798 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
1799
1800 let mut config = Config::new(&tmp_file_path, 128 * 1024); config.storage_backend(crate::StorageBackend::Std);
1802 config.cb_min_record_size = min_record_size + 2 * std::mem::size_of::<usize>();
1803 config.cb_max_record_size = max_record_size;
1804 config.leaf_page_size = leaf_page_size;
1805 config.max_fence_len = min_record_size + 2 * std::mem::size_of::<usize>();
1806 config.use_snapshot(true);
1807
1808 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
1809 let finish = Arc::new(AtomicBool::new(false));
1810
1811 let handles: Vec<_> = (0..num_threads)
1812 .map(|i| {
1813 let finish_clone = finish.clone();
1814 let bftree_clone = bftree.clone();
1815
1816 thread::spawn(move || {
1817 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
1818 assert!(key_len * 2 <= max_record_size);
1819 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
1820
1821 let mut r: usize = 0;
1822 while !finish_clone.load(Ordering::Relaxed) {
1823 key_buffer.fill(r);
1824 key_buffer[0] = i;
1825
1826 match bftree_clone.insert(
1827 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1828 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1829 ) {
1830 crate::LeafInsertResult::Success => {}
1831 _ => {
1832 panic!("Insert failed");
1833 }
1834 }
1835 r += 1;
1836 }
1837 r
1838 })
1839 })
1840 .collect();
1841
1842 thread::sleep(std::time::Duration::from_secs(5));
1843 for _ in 0..snapshot_num {
1844 let _ = std::fs::remove_file(&tmp_snapshot_file_path);
1846 bftree.cpr_snapshot(&tmp_snapshot_file_path);
1847 thread::sleep(std::time::Duration::from_secs(5));
1848 }
1849
1850 let mut rs = vec![0usize; num_threads];
1852 finish.store(true, Ordering::Relaxed);
1853 for (i, h) in handles.into_iter().enumerate() {
1854 let r = h.join().unwrap();
1855 rs[i] = r;
1856 }
1857
1858 verify_snapshot_recovery(
1859 &tmp_snapshot_file_path,
1860 num_threads,
1861 min_record_size,
1862 &rs,
1863 true,
1864 );
1865
1866 std::fs::remove_file(tmp_file_path).unwrap();
1867 std::fs::remove_file(tmp_snapshot_file_path).unwrap();
1868 }
1869
1870 #[test]
1872 fn cpr_snapshot_cache_only() {
1873 let min_record_size: usize = 64;
1874 let max_record_size: usize = 2408;
1875 let leaf_page_size: usize = 8192;
1876 let num_threads: usize = 4;
1877
1878 let snapshot_file_path: String =
1879 "target/test_simple_cache_only_snapshot.bftree".to_string();
1880 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
1881
1882 let mut config = Config::default(); config.storage_backend(crate::StorageBackend::Memory);
1884 config.file_path(":memory:");
1885 config.cache_only = true;
1886 config.cb_size_byte(1024 * 1024 * 1024);
1887 config.cb_min_record_size = min_record_size;
1888 config.cb_max_record_size = max_record_size;
1889 config.leaf_page_size = leaf_page_size;
1890 config.max_fence_len = max_record_size;
1891 config.use_snapshot(true);
1892
1893 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
1894 let finish = Arc::new(AtomicBool::new(false));
1895
1896 let handles: Vec<_> = (0..num_threads)
1897 .map(|i| {
1898 let finish_clone = finish.clone();
1899 let bftree_clone = bftree.clone();
1900
1901 thread::spawn(move || {
1902 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
1903 assert!(key_len * 2 <= max_record_size);
1904 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
1905
1906 let mut r: usize = 0;
1907 while !finish_clone.load(Ordering::Relaxed) {
1908 key_buffer.fill(r);
1909 key_buffer[0] = i;
1910
1911 match bftree_clone.insert(
1912 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1913 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1914 ) {
1915 crate::LeafInsertResult::Success => {}
1916 _ => {
1917 panic!("Insert failed");
1918 }
1919 }
1920 r += 1;
1921 }
1922 r
1923 })
1924 })
1925 .collect();
1926
1927 thread::sleep(std::time::Duration::from_secs(5));
1928 bftree.cpr_snapshot(&tmp_snapshot_file_path);
1930 thread::sleep(std::time::Duration::from_secs(5));
1931
1932 let mut rs = vec![0usize; num_threads];
1934 finish.store(true, Ordering::Relaxed);
1935 for (i, h) in handles.into_iter().enumerate() {
1936 let r = h.join().unwrap();
1937 rs[i] = r;
1938 }
1939
1940 verify_snapshot_recovery(
1941 &tmp_snapshot_file_path,
1942 num_threads,
1943 min_record_size,
1944 &rs,
1945 false,
1946 );
1947
1948 std::fs::remove_file(tmp_snapshot_file_path).unwrap();
1949 }
1950
1951 fn verify_snapshot_recovery(
1952 snapshot_file: impl AsRef<std::path::Path>,
1953 num_threads: usize,
1954 min_record_size: usize,
1955 records_num_per_threads: &Vec<usize>,
1956 check_prefix: bool,
1957 ) {
1958 let bftree = BfTree::new_from_cpr_snapshot(snapshot_file, false, None, None, None)
1959 .expect("fail to recover from snapshot");
1960
1961 let mut rs_captured = vec![0usize; num_threads];
1962 for i in 0..num_threads {
1963 let record_num = records_num_per_threads[i];
1964
1965 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
1966 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
1967 let mut res_buffer = vec![0u8; key_len];
1968 let mut not_included = false;
1969 let mut first_gap_record: Option<usize> = None;
1970
1971 for r in 0..record_num {
1972 key_buffer.fill(r);
1973 key_buffer[0] = i;
1974
1975 match bftree.read(
1976 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1977 &mut res_buffer,
1978 ) {
1979 LeafReadResult::Found(v) => {
1980 if check_prefix && not_included {
1981 let mut gaps = Vec::new();
1983 let mut found_after = Vec::new();
1984 let gap_start = first_gap_record.unwrap();
1985 let scan_end = std::cmp::min(r + 50, record_num);
1987 for scan_r in gap_start..scan_end {
1988 key_buffer.fill(scan_r);
1989 key_buffer[0] = i;
1990 match bftree.read(
1991 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
1992 &mut res_buffer,
1993 ) {
1994 LeafReadResult::Found(_) => {
1995 found_after.push(scan_r);
1996 }
1997 LeafReadResult::NotFound => {
1998 gaps.push(scan_r);
1999 }
2000 _ => {}
2001 }
2002 }
2003 panic!(
2004 "PREFIX VIOLATION: thread={}, first_gap_at={}, found_record_after_gap={}, \
2005 total_captured_before_gap={}, total_records={}\n\
2006 Gaps in [{}, {}): {:?}\n\
2007 Found in [{}, {}): {:?}",
2008 i, gap_start, r, rs_captured[i], record_num,
2009 gap_start, scan_end, &gaps[..std::cmp::min(gaps.len(), 20)],
2010 gap_start, scan_end, &found_after[..std::cmp::min(found_after.len(), 20)],
2011 );
2012 }
2013 assert_eq!(v as usize, key_len);
2014 assert_eq!(
2015 &res_buffer,
2016 bytemuck::must_cast_slice::<usize, u8>(&key_buffer)
2017 );
2018 rs_captured[i] += 1;
2019 }
2020 LeafReadResult::NotFound => {
2021 if !not_included {
2022 not_included = true;
2023 first_gap_record = Some(r);
2024 }
2025 }
2026 _ => {
2027 panic!("Unexpected read result")
2028 }
2029 }
2030 }
2031
2032 assert!(rs_captured[i] <= record_num);
2033 println!("Total inserted records for thread {}: {}", i, record_num);
2034 println!(
2035 "Hit ratio for thread {}: {}",
2036 i,
2037 rs_captured[i] as f64 / record_num as f64
2038 );
2039 }
2040 }
2041
2042 #[cfg(feature = "shuttle")]
2046 fn shuttle_cpr_snapshot_cache_only_inner(iter: usize) {
2047 let min_record_size: usize = 64;
2048 let max_record_size: usize = 2408;
2049 let leaf_page_size: usize = 8192;
2050 let num_threads: usize = 4;
2051 let inserts_per_thread: usize = 1_000; let snapshot_file_path: String = format!(
2055 "target/shuttle_cpr_snapshot_cache_only_{}_{}.bftree",
2056 std::process::id(),
2057 iter,
2058 );
2059 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
2060
2061 let mut config = Config::default();
2062 config.storage_backend(crate::StorageBackend::Memory);
2063 config.file_path(":memory:");
2064 config.cache_only = true;
2065 config.cb_size_byte(1024 * 1024 * 1024);
2068 config.cb_min_record_size = min_record_size;
2069 config.cb_max_record_size = max_record_size;
2070 config.leaf_page_size = leaf_page_size;
2071 config.max_fence_len = max_record_size;
2072 config.use_snapshot(true);
2073
2074 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
2075 let mut rs = vec![0usize; num_threads];
2076
2077 for j in 0..2 {
2078 let handles: Vec<_> = (0..num_threads)
2079 .map(|i| {
2080 let bftree_clone = bftree.clone();
2081 let start_id = j * inserts_per_thread;
2082 let end_id = start_id + inserts_per_thread;
2083 thread::spawn(move || {
2084 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
2085 assert!(key_len * 2 <= max_record_size);
2086 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
2087
2088 for r in start_id..end_id {
2089 key_buffer.fill(r);
2090 key_buffer[0] = i;
2091
2092 match bftree_clone.insert(
2093 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2094 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2095 ) {
2096 crate::LeafInsertResult::Success => {}
2097 _ => {
2098 panic!("Insert failed");
2099 }
2100 }
2101 }
2102 inserts_per_thread
2103 })
2104 })
2105 .collect();
2106
2107 let bftree_for_snap = bftree.clone();
2109 let snap_path = tmp_snapshot_file_path.clone();
2110 let snap_handle = thread::spawn(move || {
2111 bftree_for_snap.cpr_snapshot(&snap_path);
2112 });
2113
2114 for (i, h) in handles.into_iter().enumerate() {
2115 let r = h.join().unwrap();
2116 rs[i] += r;
2117 }
2118
2119 snap_handle.join().unwrap();
2121 let snap_path = tmp_snapshot_file_path.clone();
2122 verify_snapshot_recovery(&snap_path, num_threads, min_record_size, &rs, false);
2123 }
2124
2125 let _ = std::fs::remove_file(tmp_snapshot_file_path);
2126 }
2127
2128 #[cfg(feature = "shuttle")]
2130 #[test]
2131 fn shuttle_cpr_snapshot_cache_only() {
2132 use std::sync::atomic::AtomicUsize;
2133
2134 static ITER: AtomicUsize = AtomicUsize::new(0);
2137
2138 let mut shuttle_config = shuttle::Config::default();
2139 shuttle_config.max_steps = shuttle::MaxSteps::None;
2141 shuttle_config.stack_size = 1024 * 1024 * 1024; shuttle_config.failure_persistence =
2143 shuttle::FailurePersistence::File(Some(PathBuf::from_str("target").unwrap()));
2144
2145 let mut runner = shuttle::PortfolioRunner::new(true, shuttle_config);
2146 let available_cores = std::thread::available_parallelism().unwrap().get().min(4);
2147 for _ in 0..available_cores {
2148 runner.add(shuttle::scheduler::PctScheduler::new(10, 1000));
2149 }
2150
2151 runner.run(|| {
2152 let iter = ITER.fetch_add(1, Ordering::Relaxed);
2153 shuttle_cpr_snapshot_cache_only_inner(iter);
2154 eprintln!("Completed shuttle iteration {}", iter);
2155 });
2156 }
2157
2158 #[cfg(feature = "shuttle")]
2162 fn shuttle_cpr_snapshot_disk_inner(iter: usize) {
2163 let min_record_size: usize = 64;
2164 let max_record_size: usize = 2408;
2165 let leaf_page_size: usize = 8192;
2166 let num_threads: usize = 4;
2167 let inserts_per_thread: usize = 500;
2171
2172 let file_path: String = format!(
2173 "target/shuttle_cpr_snapshot_disk_{}_{}.bftree",
2174 std::process::id(),
2175 iter,
2176 );
2177 let snapshot_file_path: String = format!(
2178 "target/shuttle_cpr_snapshot_disk_{}_{}_snap.bftree",
2179 std::process::id(),
2180 iter,
2181 );
2182 let tmp_file_path = std::path::PathBuf::from_str(&file_path).unwrap();
2183 let tmp_snapshot_file_path = std::path::PathBuf::from_str(&snapshot_file_path).unwrap();
2184
2185 let mut config = Config::new(&tmp_file_path, 128 * 1024); config.storage_backend(crate::StorageBackend::Std);
2187 config.cb_min_record_size = min_record_size + 2 * std::mem::size_of::<usize>();
2188 config.cb_max_record_size = max_record_size;
2189 config.leaf_page_size = leaf_page_size;
2190 config.max_fence_len = min_record_size + 2 * std::mem::size_of::<usize>();
2191 config.use_snapshot(true);
2192
2193 let bftree = Arc::new(BfTree::with_config(config.clone(), None).unwrap());
2194 let mut rs = vec![0usize; num_threads];
2195 for j in 0..3 {
2196 let handles: Vec<_> = (0..num_threads)
2197 .map(|i| {
2198 let bftree_clone = bftree.clone();
2199 let start_id = j * inserts_per_thread;
2202 let end_id = start_id + inserts_per_thread;
2203 thread::spawn(move || {
2204 let key_len: usize = min_record_size / 2 + std::mem::size_of::<usize>();
2205 assert!(key_len * 2 <= max_record_size);
2206 let mut key_buffer = vec![0usize; key_len / std::mem::size_of::<usize>()];
2207
2208 for r in start_id..end_id {
2209 key_buffer.fill(r);
2210 key_buffer[0] = i;
2211
2212 match bftree_clone.insert(
2213 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2214 bytemuck::must_cast_slice::<usize, u8>(&key_buffer),
2215 ) {
2216 crate::LeafInsertResult::Success => {}
2217 _ => {
2218 panic!("Insert failed");
2219 }
2220 }
2221 }
2222 inserts_per_thread
2223 })
2224 })
2225 .collect();
2226
2227 let bftree_for_snap = bftree.clone();
2229 let snap_path = tmp_snapshot_file_path.clone();
2230 let snap_handle = thread::spawn(move || {
2231 bftree_for_snap.cpr_snapshot(&snap_path);
2232 });
2233
2234 for (i, h) in handles.into_iter().enumerate() {
2235 let r = h.join().unwrap();
2236 rs[i] += r;
2237 }
2238
2239 snap_handle.join().unwrap();
2240
2241 verify_snapshot_recovery(
2243 &tmp_snapshot_file_path,
2244 num_threads,
2245 min_record_size,
2246 &rs,
2247 true,
2248 );
2249 }
2250 let _ = std::fs::remove_file(tmp_file_path);
2251 let _ = std::fs::remove_file(tmp_snapshot_file_path);
2252 }
2253
2254 #[cfg(feature = "shuttle")]
2255 #[test]
2256 fn shuttle_cpr_snapshot_disk() {
2257 use std::sync::atomic::AtomicUsize;
2258
2259 static ITER: AtomicUsize = AtomicUsize::new(0);
2262
2263 let mut shuttle_config = shuttle::Config::default();
2264 shuttle_config.max_steps = shuttle::MaxSteps::None;
2265 shuttle_config.stack_size = 4 * 1024 * 1024; shuttle_config.failure_persistence =
2270 shuttle::FailurePersistence::File(Some(PathBuf::from_str("target").unwrap()));
2271
2272 let mut runner = shuttle::PortfolioRunner::new(true, shuttle_config);
2273 let available_cores = std::thread::available_parallelism().unwrap().get().min(4);
2274 for _ in 0..available_cores {
2275 runner.add(shuttle::scheduler::PctScheduler::new(10, 100));
2276 }
2277
2278 runner.run(|| {
2279 let iter = ITER.fetch_add(1, Ordering::Relaxed);
2280 shuttle_cpr_snapshot_disk_inner(iter);
2281 });
2282 }
2283
2284 #[cfg(feature = "shuttle")]
2285 #[test]
2286 fn shuttle_replay() {
2287 let schedule_path = "target/schedule000.txt";
2288 if !std::path::Path::new(schedule_path).exists() {
2289 eprintln!("No schedule file at {schedule_path}; run shuttle_cpr_snapshot_disk to generate one on failure.");
2290 return;
2291 }
2292
2293 tracing_subscriber::fmt()
2295 .with_ansi(true)
2296 .with_thread_names(false)
2297 .with_target(false)
2298 .init();
2299
2300 shuttle::replay_from_file(|| shuttle_cpr_snapshot_disk_inner(0), schedule_path);
2301 }
2302}