use std::{
fs,
path::Path,
sync::atomic::Ordering,
time::{Duration, Instant},
};
use wbase::backoff::backoff;
use super::RangeIndexManager;
use crate::{
error::{Error, Result},
service::BfTreeService,
};
fn spin_until(cond: impl Fn() -> bool, deadline: Instant) -> bool {
let mut spins = 0u32;
while !cond() {
if Instant::now() >= deadline {
return false;
}
backoff(spins);
spins = spins.wrapping_add(1);
}
true
}
pub(crate) 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)
}
pub fn wait_for_global_checkpoint(&self) -> Result<()> {
self.wait_for_global_checkpoint_within(CHECKPOINT_WAIT_TIMEOUT)
}
pub fn wait_for_global_checkpoint_within(&self, timeout: Duration) -> Result<()> {
let deadline = Instant::now() + timeout;
if spin_until(|| !self.is_checkpoint_in_progress(), deadline) {
Ok(())
} else {
Err(Error::Timeout)
}
}
pub fn snapshot_all_trees_for_checkpoint(&self, checkpoint_token: u128) -> 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: u128,
) -> Result<usize> {
if !self.is_checkpoint_in_progress() {
self.set_checkpoint_barrier();
}
let _guard = CheckpointGuard(self);
let entries = self.live_entries();
let mut token_dest = Self::token_snapshot_dir(target_dir, checkpoint_token);
fs::create_dir_all(&token_dest)?;
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();
let prefix = entry.hash_prefix();
dest_file_name.clear();
dest_file_name.push_str(prefix.as_str());
dest_file_name.push_str(super::TREE_FILE_SUFFIX);
token_dest.push(&dest_file_name);
let res = if let Some(tree) = tree_opt {
entry.snapshot_under_claim(&tree, &token_dest).map(|()| {
snapshot_count += 1;
})
} else {
let data_path = self.data_file_path(&prefix);
if data_path.exists() {
fs::copy(&data_path, &token_dest).map(|_| {
snapshot_count += 1;
})?
}
Ok(())
};
token_dest.pop();
res?;
}
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),
}
}
}