use std::{
fs,
panic::{self, AssertUnwindSafe},
path::{Path, PathBuf},
result::Result as StdResult,
sync::{
Arc,
atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
},
thread::yield_now,
};
use bf_tree::{BfTree, ConfigError, LeafInsertResult, LeafReadResult, ScanIter, ScanIterError};
use parking_lot::RwLock;
use crate::{
error::{Error, Result},
types::{
BfTreeConfig, BfTreeDeleteResult, BfTreeInsertResult, BfTreeReadResult, ScanRecord,
ScanReturnField, StorageBackendType,
},
};
const STACK_READ_BUF_SIZE: usize = 4096;
const STACK_SCAN_BUF_SIZE: usize = 8192;
const SNAPSHOT_DRAIN_SPINS: u32 = 64;
fn snapshot_missing(path: &Path) -> Error {
let mut msg = String::from(SNAPSHOT_MISSING_PREFIX);
msg.push_str(&path.display().to_string());
Error::Recovery(msg)
}
const PRESET_LEAF_PAGE_SIZE: usize = 16384;
const PRESET_MAX_RECORD_SIZE: usize = 4096;
const PRESET_MAX_KEY_LEN: usize = 512;
const PRESET_MIN_RECORD_SIZE: usize = 4;
const SNAPSHOT_MISSING_PREFIX: &str = "快照文件不存在: ";
const MIN_MAX_RECORD_SIZE: usize = STACK_READ_BUF_SIZE;
#[inline]
fn config_error_to_string(e: ConfigError) -> String {
match e {
ConfigError::MinimumRecordSize(s) => {
let mut msg = String::from("MinimumRecordSize: ");
msg.push_str(&s);
msg
}
ConfigError::MaximumRecordSize(s) => {
let mut msg = String::from("MaximumRecordSize: ");
msg.push_str(&s);
msg
}
ConfigError::LeafPageSize(s) => {
let mut msg = String::from("LeafPageSize: ");
msg.push_str(&s);
msg
}
ConfigError::MaxKeyLen(s) => {
let mut msg = String::from("MaxKeyLen: ");
msg.push_str(&s);
msg
}
ConfigError::CircularBufferSize(s) => {
let mut msg = String::from("CircularBufferSize: ");
msg.push_str(&s);
msg
}
ConfigError::SnapshotFileInvalid(s) => {
let mut msg = String::from("SnapshotFileInvalid: ");
msg.push_str(&s);
msg
}
ConfigError::SnapshotDisabled => String::from("SnapshotDisabled"),
}
}
#[inline]
fn scan_iter_error_to_string(e: ScanIterError) -> &'static str {
match e {
ScanIterError::CacheOnlyMode => "CacheOnlyMode",
ScanIterError::InvalidStartKey => "InvalidStartKey",
ScanIterError::InvalidEndKey => "InvalidEndKey",
ScanIterError::InvalidCount => "InvalidCount",
ScanIterError::InvalidKeyRange => "InvalidKeyRange",
}
}
pub struct BfTreeService {
tree: RwLock<Option<Arc<BfTree>>>,
storage_backend: AtomicU8,
file_path: RwLock<Option<String>>,
max_record_size: AtomicUsize,
disposed: AtomicBool,
writers: AtomicUsize,
barriers: AtomicUsize,
}
struct WriteGuard<'a> {
writers: &'a AtomicUsize,
}
impl<'a> WriteGuard<'a> {
#[inline]
fn acquire(service: &'a BfTreeService) -> Self {
let mut spins = 0u32;
loop {
if service.barriers.load(Ordering::Acquire) == 0 {
service.writers.fetch_add(1, Ordering::SeqCst);
if service.barriers.load(Ordering::SeqCst) == 0 {
return Self {
writers: &service.writers,
};
}
service.writers.fetch_sub(1, Ordering::Release);
}
spins = spins.wrapping_add(1);
if spins.is_multiple_of(SNAPSHOT_DRAIN_SPINS) {
yield_now();
}
}
}
}
impl Drop for WriteGuard<'_> {
#[inline]
fn drop(&mut self) {
self.writers.fetch_sub(1, Ordering::Release);
}
}
pub struct WriteBarrierGuard<'a> {
service: &'a BfTreeService,
}
impl Drop for WriteBarrierGuard<'_> {
#[inline]
fn drop(&mut self) {
self.service.barriers.fetch_sub(1, Ordering::Release);
}
}
impl BfTreeService {
pub(crate) fn new_with_backend(
config: impl Into<bf_tree::Config>,
storage_backend: StorageBackendType,
file_path: Option<String>,
) -> Result<Self> {
if storage_backend == StorageBackendType::Disk && file_path.is_none() {
return Err(Error::InvalidArgument(
"磁盘后端必须指定数据文件路径 (file_path)".into(),
));
}
let inner_cfg: bf_tree::Config = config.into();
let max_record_size = inner_cfg.get_cb_max_record_size().max(MIN_MAX_RECORD_SIZE);
let tree = match BfTree::with_config(inner_cfg, None) {
Ok(t) => Arc::new(t),
Err(e) => return Err(Error::InvalidConfig(config_error_to_string(e))),
};
Ok(Self {
tree: RwLock::new(Some(tree)),
storage_backend: AtomicU8::new(storage_backend as u8),
file_path: RwLock::new(file_path),
max_record_size: AtomicUsize::new(max_record_size),
disposed: AtomicBool::new(false),
writers: AtomicUsize::new(0),
barriers: AtomicUsize::new(0),
})
}
pub fn new(config: BfTreeConfig) -> Result<Self> {
let storage_backend = config.storage_backend;
let file_path = config.file_path;
Self::new_with_backend(config.inner, storage_backend, file_path)
}
fn preset_config(cb_min_record_size: usize) -> BfTreeConfig {
let mut config = BfTreeConfig::default();
config
.use_snapshot(true)
.leaf_page_size(PRESET_LEAF_PAGE_SIZE)
.cb_max_record_size(PRESET_MAX_RECORD_SIZE)
.cb_max_key_len(PRESET_MAX_KEY_LEN)
.cb_min_record_size(if cb_min_record_size > 0 {
cb_min_record_size
} else {
PRESET_MIN_RECORD_SIZE
});
config
}
pub fn open_disk(path: impl AsRef<Path>, cb_min_record_size: usize) -> Result<Self> {
let p = path.as_ref();
if p.as_os_str().is_empty() {
return Err(Error::InvalidArgument(
"磁盘后端必须指定有效的数据文件路径".into(),
));
}
if let Some(parent) = p.parent()
&& !parent.as_os_str().is_empty()
{
fs::create_dir_all(parent)?;
}
let mut config = Self::preset_config(cb_min_record_size);
config.file_path(p);
Self::new_with_backend(
config,
StorageBackendType::Disk,
Some(p.to_string_lossy().into_owned()),
)
}
pub fn open_memory(cb_min_record_size: usize) -> Result<Self> {
let mut config = Self::preset_config(cb_min_record_size);
config.cache_only(true);
Self::new_with_backend(config, StorageBackendType::Memory, None)
}
#[inline]
fn tree_arc(&self) -> Result<Arc<BfTree>> {
self.check_disposed()?;
self.tree.read().as_ref().cloned().ok_or(Error::Disposed)
}
#[inline]
fn with_tree<R>(&self, f: impl FnOnce(&BfTree) -> R) -> Result<R> {
self.check_disposed()?;
let guard = self.tree.read();
match guard.as_ref() {
Some(tree) => Ok(f(tree)),
None => Err(Error::Disposed),
}
}
#[inline]
pub fn native_ptr(&self) -> u64 {
if let Some(tree) = self.tree.read().as_ref() {
Arc::as_ptr(tree) as usize as u64
} else {
0
}
}
#[inline]
pub fn file_path(&self) -> Option<String> {
self.file_path.read().clone()
}
#[inline]
pub fn storage_backend(&self) -> StorageBackendType {
StorageBackendType::from_u8(self.storage_backend.load(Ordering::Acquire))
}
#[inline]
fn max_record_size(&self) -> usize {
self.max_record_size.load(Ordering::Relaxed)
}
#[inline]
pub fn is_disposed(&self) -> bool {
self.disposed.load(Ordering::Acquire)
}
#[inline]
fn check_disposed(&self) -> Result<()> {
if self.is_disposed() {
Err(Error::Disposed)
} else {
Ok(())
}
}
#[inline]
pub fn insert(&self, key: &[u8], value: &[u8]) -> BfTreeInsertResult {
if value.is_empty() {
return BfTreeInsertResult::InvalidKV;
}
let _guard = WriteGuard::acquire(self);
self
.with_tree(|tree| match tree.insert(key, value) {
LeafInsertResult::Success => BfTreeInsertResult::Success,
LeafInsertResult::InvalidKV(_) => BfTreeInsertResult::InvalidKV,
})
.unwrap_or(BfTreeInsertResult::InvalidArguments)
}
pub fn read(&self, key: &[u8]) -> (BfTreeReadResult, Option<Vec<u8>>) {
let max_record_size = self.max_record_size.load(Ordering::Relaxed);
if max_record_size <= STACK_READ_BUF_SIZE {
let mut stack_buf = [0u8; STACK_READ_BUF_SIZE];
let (res, len) = self.read_direct(key, &mut stack_buf);
(
res,
(res == BfTreeReadResult::Found).then(|| stack_buf[..len].to_vec()),
)
} else {
let mut heap_buf = vec![0u8; max_record_size];
let (res, len) = self.read_direct(key, &mut heap_buf);
if res == BfTreeReadResult::Found {
heap_buf.truncate(len);
heap_buf.shrink_to_fit();
(res, Some(heap_buf))
} else {
(res, None)
}
}
}
#[inline]
fn read_direct(&self, key: &[u8], out_buf: &mut [u8]) -> (BfTreeReadResult, usize) {
self
.with_tree(|tree| match tree.read(key, out_buf) {
LeafReadResult::Found(n) => (BfTreeReadResult::Found, n as usize),
LeafReadResult::NotFound => (BfTreeReadResult::NotFound, 0),
LeafReadResult::Deleted => (BfTreeReadResult::Deleted, 0),
LeafReadResult::InvalidKey => (BfTreeReadResult::InvalidKey, 0),
})
.unwrap_or((BfTreeReadResult::InvalidArguments, 0))
}
#[inline]
fn read_via_scratch(
&self,
key: &[u8],
out_buf: &mut [u8],
scratch: &mut [u8],
) -> (BfTreeReadResult, usize) {
let (res, len) = self.read_direct(key, scratch);
match res {
BfTreeReadResult::Found if out_buf.len() >= len => {
out_buf[..len].copy_from_slice(&scratch[..len]);
(BfTreeReadResult::Found, len)
}
BfTreeReadResult::Found => (BfTreeReadResult::InvalidArguments, 0),
r => (r, 0),
}
}
pub fn read_into(&self, key: &[u8], out_buf: &mut [u8]) -> (BfTreeReadResult, usize) {
let max_record_size = self.max_record_size.load(Ordering::Relaxed);
if out_buf.len() >= max_record_size {
return self.read_direct(key, out_buf);
}
if max_record_size <= STACK_READ_BUF_SIZE {
let mut stack_buf = [0u8; STACK_READ_BUF_SIZE];
self.read_via_scratch(key, out_buf, &mut stack_buf)
} else {
let mut heap_buf = vec![0u8; max_record_size];
self.read_via_scratch(key, out_buf, &mut heap_buf)
}
}
#[inline]
pub fn delete(&self, key: &[u8]) -> BfTreeDeleteResult {
let _guard = WriteGuard::acquire(self);
self
.with_tree(|tree| {
tree.delete(key);
BfTreeDeleteResult::Success
})
.unwrap_or(BfTreeDeleteResult::InvalidArguments)
}
pub fn scan_with_count_callback<F>(
&self,
start_key: &[u8],
count: usize,
return_field: ScanReturnField,
on_record: F,
) -> Result<usize>
where
F: FnMut(&[u8], &[u8]) -> bool,
{
if count == 0 {
return Ok(0);
}
self.scan_callback(
|tree| tree.scan_with_count(start_key, count, return_field),
return_field,
on_record,
)
}
pub fn scan_with_count(
&self,
start_key: &[u8],
count: usize,
return_field: ScanReturnField,
) -> Result<Vec<ScanRecord>> {
let mut records = Vec::with_capacity(count.min(1024));
self.scan_with_count_callback(start_key, count, return_field, |k, v| {
records.push(ScanRecord {
key: k.to_vec(),
value: v.to_vec(),
});
true
})?;
Ok(records)
}
pub fn scan_with_end_key_callback<F>(
&self,
start_key: &[u8],
end_key: &[u8],
return_field: ScanReturnField,
on_record: F,
) -> Result<usize>
where
F: FnMut(&[u8], &[u8]) -> bool,
{
if start_key > end_key {
return Ok(0);
}
self.scan_callback(
|tree| tree.scan_with_end_key(start_key, end_key, return_field),
return_field,
on_record,
)
}
pub fn scan_with_end_key(
&self,
start_key: &[u8],
end_key: &[u8],
return_field: ScanReturnField,
) -> Result<Vec<ScanRecord>> {
let mut records = Vec::with_capacity(32);
self.scan_with_end_key_callback(start_key, end_key, return_field, |k, v| {
records.push(ScanRecord {
key: k.to_vec(),
value: v.to_vec(),
});
true
})?;
Ok(records)
}
pub fn scan_all(&self, return_field: ScanReturnField) -> Result<Vec<ScanRecord>> {
self.scan_with_count(&[0], usize::MAX, return_field)
}
fn scan_callback<F>(
&self,
make_iter: impl FnOnce(&BfTree) -> StdResult<ScanIter<'_, '_>, ScanIterError>,
return_field: ScanReturnField,
mut on_record: F,
) -> Result<usize>
where
F: FnMut(&[u8], &[u8]) -> bool,
{
let tree = self.tree_arc()?;
let mut iter = make_iter(&tree)
.map_err(|e| Error::InvalidArgument(scan_iter_error_to_string(e).to_string()))?;
let mut stack_buf = [0u8; STACK_SCAN_BUF_SIZE];
let mut heap_buf;
let max_record_size = self.max_record_size.load(Ordering::Relaxed);
let buf: &mut [u8] = if max_record_size <= STACK_SCAN_BUF_SIZE {
&mut stack_buf
} else {
heap_buf = vec![0u8; max_record_size];
&mut heap_buf
};
let mut scanned = 0;
while let Some((k_len, v_len)) = iter.next(buf) {
let k = if return_field != ScanReturnField::Value {
&buf[..k_len]
} else {
&[]
};
let v = if return_field != ScanReturnField::Key {
&buf[k_len..k_len + v_len]
} else {
&[]
};
scanned += 1;
if !on_record(k, v) {
break;
}
}
Ok(scanned)
}
fn drain_writers(&self) {
let mut spins = 0u32;
while self.writers.load(Ordering::SeqCst) != 0 {
spins = spins.wrapping_add(1);
if spins.is_multiple_of(SNAPSHOT_DRAIN_SPINS) {
yield_now();
}
}
}
pub fn write_barrier(&self) -> WriteBarrierGuard<'_> {
self.barriers.fetch_add(1, Ordering::SeqCst);
WriteBarrierGuard { service: self }
}
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)?;
}
let _guard = self.write_barrier();
self.drain_writers();
let res = panic::catch_unwind(AssertUnwindSafe(|| tree.cpr_snapshot(p)))
.map_err(|_| Error::Snapshot("底层引擎异常 (快照未启用或内部状态异常)".into()));
drop(_guard);
res
}
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 _barrier = self.write_barrier();
self.drain_writers();
let mut guard = self.tree.write();
if self.disposed.load(Ordering::Acquire) {
return Err(Error::Disposed);
}
let old_tree = guard.take();
*guard = recovered.tree.write().take();
old_tree
};
drop(old_tree);
self
.storage_backend
.store(recovered.storage_backend() as u8, Ordering::Release);
*self.file_path.write() = Some(work_path.to_string_lossy().into_owned());
self
.max_record_size
.store(recovered.max_record_size(), Ordering::Release);
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));
}
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);
Ok(Self {
tree: RwLock::new(Some(Arc::new(tree))),
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),
writers: AtomicUsize::new(0),
barriers: AtomicUsize::new(0),
})
}
Ok(Err(e)) => Err(Error::Recovery(config_error_to_string(e))),
Err(_) => {
let mut msg = String::from("快照文件损坏或格式非法: ");
msg.push_str(&p.display().to_string());
Err(Error::Recovery(msg))
}
}
}
pub fn dispose(&self) {
if !self.disposed.swap(true, Ordering::SeqCst) {
self.tree.write().take();
}
}
}
impl Drop for BfTreeService {
fn drop(&mut self) {
self.dispose();
}
}