use std::{fs, path::Path, sync::atomic::Ordering};
use wbase::{
backoff::backoff,
time::{Duration, Instant},
};
use super::RangeIndexManager;
use crate::{
error::{Error, Result},
service::{BfTreeService, spin_until},
};
const CHECKPOINT_WAIT_TIMEOUT: Duration = Duration::from_secs(30);
struct CheckpointGuard<'a>(&'a RangeIndexManager);
impl Drop for CheckpointGuard<'_> {
fn drop(&mut self) {
self.0.clear_checkpoint_barrier();
}
}
struct SnapshotPendingGuard<'a>(&'a super::TreeEntry);
impl Drop for SnapshotPendingGuard<'_> {
fn drop(&mut self) {
self.0.snapshot_pending.store(false, Ordering::SeqCst);
}
}
impl RangeIndexManager {
pub fn set_checkpoint_barrier(&self) {
let pin = self.live_indexes.pin();
for entry in pin.values() {
entry.snapshot_pending.store(true, Ordering::SeqCst);
}
self.checkpoint_in_progress.store(true, Ordering::SeqCst);
}
pub fn clear_checkpoint_barrier(&self) {
self.checkpoint_in_progress.store(false, Ordering::SeqCst);
let pin = self.live_indexes.pin();
for entry in pin.values() {
entry.snapshot_pending.store(false, Ordering::SeqCst);
}
}
pub fn wait_for_tree_checkpoint(&self, key: &[u8]) -> Result<bool> {
if !self.checkpoint_in_progress.load(Ordering::Acquire) {
return Ok(false);
}
let key_id = Self::key_id_of(key);
let entry_opt = {
let pin = self.live_indexes.pin();
pin.get(&key_id).cloned()
};
if let Some(entry) = entry_opt
&& entry.snapshot_pending.load(Ordering::Acquire)
{
let deadline = Instant::now() + CHECKPOINT_WAIT_TIMEOUT;
if spin_until(|| !entry.snapshot_pending.load(Ordering::Acquire), deadline) {
return Ok(true);
}
return Err(Error::Timeout);
}
Ok(false)
}
#[inline]
pub fn wait_for_global_checkpoint(&self) {
let mut spins = 0u32;
while self.is_checkpoint_in_progress() {
backoff(spins);
spins = spins.wrapping_add(1);
}
}
pub fn snapshot_all_trees_for_checkpoint(&self, checkpoint_token: &str) -> Result<()> {
self.snapshot_all_trees_to_dir(&self.cpr_dir, checkpoint_token)?;
Ok(())
}
pub fn snapshot_all_trees_to_dir(
&self,
target_dir: &Path,
checkpoint_token: &str,
) -> Result<usize> {
if !self.is_checkpoint_in_progress() {
self.set_checkpoint_barrier();
}
let _guard = CheckpointGuard(self);
let entries = self.live_entries();
let token_snapshot_dir = target_dir.join(checkpoint_token).join("rangeindex");
let _ = fs::create_dir_all(&token_snapshot_dir);
let mut snapshot_count = 0;
let mut dest_file_name =
String::with_capacity(super::HASH_PREFIX_LEN + super::TREE_FILE_SUFFIX.len());
for entry in &entries {
if !entry.snapshot_pending.load(Ordering::Acquire) {
continue;
}
let _pending_guard = SnapshotPendingGuard(entry);
let tree_opt = entry.tree.read().as_ref().cloned();
dest_file_name.clear();
dest_file_name.push_str(entry.hash_prefix.as_str());
dest_file_name.push_str(super::TREE_FILE_SUFFIX);
let token_dest = token_snapshot_dir.join(&dest_file_name);
if let Some(tree) = tree_opt {
entry.snapshot_under_claim(&tree, &token_dest)?;
snapshot_count += 1;
} else {
let data_path = self.data_file_path(&entry.hash_prefix);
if data_path.exists() {
fs::copy(&data_path, &token_dest)?;
snapshot_count += 1;
}
}
}
Ok(snapshot_count)
}
pub fn snapshot_tree_to_path_locked(
&self,
key: &[u8],
tree: &BfTreeService,
destination_path: &Path,
) -> Result<()> {
let key_id = Self::key_id_of(key);
let entry = self.live_indexes.pin().get(&key_id).cloned();
match entry {
Some(entry) => entry.snapshot_under_claim(tree, destination_path),
None => tree.cpr_snapshot(destination_path),
}
}
}