use std::{
fs,
panic::{self, AssertUnwindSafe},
path::{Path, PathBuf},
ptr::null_mut,
sync::{
Arc,
atomic::{AtomicBool, AtomicPtr, AtomicU8, AtomicUsize, Ordering},
},
};
use bf_tree::BfTree;
use parking_lot::RwLock;
use super::{BfTreeService, MIN_MAX_RECORD_SIZE, config_error_to_string, file_has_cpr_magic};
use crate::{
error::{Error, Result},
types::StorageBackendType,
};
fn format_recovery_err(prefix: &str, path: &Path) -> Error {
use core::fmt::Write;
let mut msg = String::with_capacity(prefix.len() + path.as_os_str().len());
let _ = write!(msg, "{prefix}{}", path.display());
Error::Recovery(msg)
}
#[inline]
fn snapshot_missing(path: &Path) -> Error {
format_recovery_err("快照文件不存在: ", path)
}
impl BfTreeService {
pub fn cpr_snapshot(&self, snapshot_path: impl AsRef<Path>) -> Result<()> {
let tree = self.tree_arc()?;
let p = snapshot_path.as_ref();
if let Some(parent) = p.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent)?;
}
panic::catch_unwind(AssertUnwindSafe(|| tree.cpr_snapshot(p)))
.map_err(|_| Error::Snapshot("底层引擎异常 (快照未启用或内部状态异常)".into()))
}
pub unsafe fn cpr_snapshot_by_ptr(tree_ptr: u64, snapshot_path: impl AsRef<Path>) -> Result<()> {
if tree_ptr == 0 {
return Err(Error::InvalidArgument("原生树句柄为空".into()));
}
let tree = unsafe { &*(tree_ptr as usize as *const BfTree) };
let p = snapshot_path.as_ref();
if let Some(parent) = p.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent)?;
}
panic::catch_unwind(AssertUnwindSafe(|| tree.cpr_snapshot(p)))
.map_err(|_| Error::Snapshot("底层引擎异常 (快照未启用或内部状态异常)".into()))
}
pub fn recover_in_place(&self, snapshot_path: &Path, work_path: &Path) -> Result<()> {
if !snapshot_path.exists() {
return Err(snapshot_missing(snapshot_path));
}
if let Some(parent) = work_path.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent)?;
}
let mut tmp_os = work_path.as_os_str().to_os_string();
tmp_os.push(".recovering");
let tmp_path = PathBuf::from(tmp_os);
fs::copy(snapshot_path, &tmp_path)?;
let recovered = match Self::recover_from_cpr_snapshot(&tmp_path, true, StorageBackendType::Disk)
{
Ok(tree) => tree,
Err(e) => {
let _ = fs::remove_file(&tmp_path);
return Err(e);
}
};
if self.disposed.load(Ordering::Acquire) {
let _ = fs::remove_file(&tmp_path);
return Err(Error::Disposed);
}
if let Err(e) = fs::rename(&tmp_path, work_path) {
let _ = fs::remove_file(&tmp_path);
return Err(e.into());
}
let old_tree = {
let mut guard = self.arc_tree.write();
if self.disposed.load(Ordering::Acquire) {
return Err(Error::Disposed);
}
let old_tree = guard.take();
let new_tree = recovered.arc_tree.write().take();
let raw_ptr = new_tree
.as_ref()
.map(|t| Arc::as_ptr(t) as *mut BfTree)
.unwrap_or(null_mut());
self.raw_tree.store(raw_ptr, Ordering::SeqCst);
*guard = new_tree;
self
.max_record_size
.store(recovered.max_record_size(), Ordering::Release);
old_tree
};
if let Some(t) = old_tree {
self.retired_trees.write().push(t);
}
self
.storage_backend
.store(recovered.storage_backend() as u8, Ordering::Release);
*self.file_path.write() = Some(work_path.to_string_lossy().into_owned());
Ok(())
}
pub fn recover_from_cpr_snapshot(
recovery_path: impl AsRef<Path>,
enable_snapshots: bool,
storage_backend: impl Into<StorageBackendType>,
) -> Result<Self> {
let p = recovery_path.as_ref();
if !p.exists() {
return Err(snapshot_missing(p));
}
if !file_has_cpr_magic(p) {
return Err(format_recovery_err(
"快照文件损坏或格式非法 (魔数不匹配): ",
p,
));
}
let backend = storage_backend.into();
let use_snapshot = enable_snapshots;
match panic::catch_unwind(AssertUnwindSafe(|| {
BfTree::new_from_cpr_snapshot(p, use_snapshot, None, None, None)
})) {
Ok(Ok(tree)) => {
let max_record_size = tree
.config()
.get_cb_max_record_size()
.max(MIN_MAX_RECORD_SIZE);
let tree_arc = Arc::new(tree);
let raw_ptr = Arc::as_ptr(&tree_arc) as *mut BfTree;
Ok(Self {
raw_tree: AtomicPtr::new(raw_ptr),
arc_tree: RwLock::new(Some(tree_arc)),
retired_trees: RwLock::new(Vec::new()),
storage_backend: AtomicU8::new(backend as u8),
file_path: RwLock::new(Some(p.to_string_lossy().into_owned())),
max_record_size: AtomicUsize::new(max_record_size),
disposed: AtomicBool::new(false),
barriers: AtomicUsize::new(0),
})
}
Ok(Err(e)) => Err(Error::Recovery(config_error_to_string(e))),
Err(_) => Err(format_recovery_err("快照文件损坏或格式非法: ", p)),
}
}
}