mod checkpoint;
mod flush;
mod lifecycle;
mod replication;
use std::{
fs,
path::{Path, PathBuf},
str,
sync::{
Arc,
atomic::{AtomicBool, AtomicU64, Ordering},
},
time::Instant,
};
use parking_lot::RwLock;
use wbase::{
backoff::backoff,
base32::{BASE32_LEN_U64, BASE32_LEN_U128, Base32Buf128, encode_u64, encode_u128},
striped::{CacheAlignedLock as BaseCacheAlignedLock, StripedRwLock},
};
use wepoch::LightEpoch;
use whasher::{GxPapayaMap, fast_hash, hash128, new_papaya_map};
use crate::{
error::{Error, Result},
service::BfTreeService,
stub::RANGE_INDEX_STUB_SIZE,
};
const PREFIX_SEED_1: u64 = 0x27bb_2ee6_87b0_b0fd;
const PREFIX_SEED_2: u64 = 0x517c_c1b7_2722_0a95;
pub(crate) const HASH_PREFIX_LEN: usize = BASE32_LEN_U128;
const DATA_FILE_SUFFIX: &str = ".data.bftree";
const FLUSH_FILE_SUFFIX: &str = ".flush.bftree";
const TREE_FILE_SUFFIX: &str = ".bftree";
pub(crate) const CPR_MAGIC: &[u8; 16] = b"BF-TREE-V0-BEGIN";
pub const NUM_LOCK_STRIPES: usize = 128;
pub const DEFAULT_MIGRATION_CHUNK_SIZE: usize = 256 * 1024;
pub const INDEX_SIZE_BYTES: usize = RANGE_INDEX_STUB_SIZE;
pub type CacheAlignedLock = BaseCacheAlignedLock<()>;
pub type RangeIndexLocks = StripedRwLock<(), NUM_LOCK_STRIPES>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RangeIndexFileEntry {
pub path: PathBuf,
pub key_id: u128,
pub address: u64,
pub is_flush_file: bool,
}
impl RangeIndexFileEntry {
#[inline(always)]
pub fn key_hash(&self) -> Base32Buf128 {
encode_u128(self.key_id)
}
}
pub struct TreeEntry {
pub tree: RwLock<Option<Arc<BfTreeService>>>,
pub key_hash: u64,
pub key_id: u128,
pub snapshot_pending: AtomicBool,
pub snapshot_in_progress: AtomicBool,
}
impl TreeEntry {
pub fn new(tree: Option<Arc<BfTreeService>>, key_hash: u64, key_id: u128) -> Self {
Self {
tree: RwLock::new(tree),
key_hash,
key_id,
snapshot_pending: AtomicBool::new(false),
snapshot_in_progress: AtomicBool::new(false),
}
}
#[inline(always)]
pub fn hash_prefix(&self) -> Base32Buf128 {
encode_u128(self.key_id)
}
#[inline]
pub fn try_claim_snapshot(&self) -> bool {
self
.snapshot_in_progress
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed)
.is_ok()
}
#[inline]
pub fn release_snapshot(&self) {
self.snapshot_in_progress.store(false, Ordering::Release);
}
pub fn snapshot_under_claim(&self, tree: &BfTreeService, destination_path: &Path) -> Result<()> {
let deadline = Instant::now() + checkpoint::CHECKPOINT_WAIT_TIMEOUT;
let mut spins = 0u32;
while !self.try_claim_snapshot() {
if Instant::now() >= deadline {
return Err(Error::Timeout);
}
backoff(spins);
spins = spins.wrapping_add(1);
}
let _guard = SnapshotClaimGuard(self);
tree.cpr_snapshot(destination_path)
}
}
struct SnapshotClaimGuard<'a>(&'a TreeEntry);
impl Drop for SnapshotClaimGuard<'_> {
fn drop(&mut self) {
self.0.release_snapshot();
}
}
pub struct RangeIndexManager {
pub(crate) ri_log_root: PathBuf,
pub(crate) cpr_dir: PathBuf,
pub(crate) migration_temp_dir: PathBuf,
pub(crate) live_indexes: GxPapayaMap<u128, Arc<TreeEntry>>,
pub(crate) checkpoint_in_progress: AtomicBool,
pub(crate) addr_flush_gen: AtomicU64,
pub(crate) addr_flush_settled_gen: AtomicU64,
pub(crate) locks: RangeIndexLocks,
pub(crate) store_epoch: Option<Arc<LightEpoch>>,
}
impl RangeIndexManager {
pub fn from_root(ri_log_root: impl Into<PathBuf>) -> Result<Self> {
let root = ri_log_root.into();
let cpr = root.join("cpr");
Self::new(root, cpr)
}
pub fn new(ri_log_root: impl Into<PathBuf>, cpr_dir: impl Into<PathBuf>) -> Result<Self> {
Self::with_epoch(ri_log_root, cpr_dir, None)
}
pub fn with_epoch(
ri_log_root: impl Into<PathBuf>,
cpr_dir: impl Into<PathBuf>,
store_epoch: Option<Arc<LightEpoch>>,
) -> Result<Self> {
let ri_log_root = ri_log_root.into();
let cpr_dir = cpr_dir.into();
if ri_log_root.as_os_str().is_empty() {
return Err(Error::InvalidArgument(
"ri_log_root 路径不能为空 (对标 C# ArgumentException)".into(),
));
}
fs::create_dir_all(&ri_log_root)?;
fs::create_dir_all(&cpr_dir)?;
let migration_temp_dir = ri_log_root.join("migration-tmp");
if migration_temp_dir.exists() {
fs::remove_dir_all(&migration_temp_dir)?;
}
fs::create_dir_all(&migration_temp_dir)?;
if let Ok(entries) = fs::read_dir(&ri_log_root) {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str()
&& name.ends_with(".recovering")
{
let _ = fs::remove_file(entry.path());
}
}
}
Ok(Self {
ri_log_root,
cpr_dir,
migration_temp_dir,
live_indexes: new_papaya_map(),
checkpoint_in_progress: AtomicBool::new(false),
addr_flush_gen: AtomicU64::new(1),
addr_flush_settled_gen: AtomicU64::new(0),
locks: RangeIndexLocks::new(),
store_epoch,
})
}
#[inline]
pub fn store_epoch(&self) -> Option<&Arc<LightEpoch>> {
self.store_epoch.as_ref()
}
#[inline]
pub(crate) fn addr_flush_scan_pending(&self) -> bool {
self.addr_flush_gen.load(Ordering::Acquire)
!= self.addr_flush_settled_gen.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn addr_flush_scan_token(&self) -> u64 {
self.addr_flush_gen.load(Ordering::Acquire)
}
#[inline]
pub(crate) fn settle_addr_flush_scan(&self, token: u64) {
if self.addr_flush_gen.load(Ordering::Acquire) == token {
self.addr_flush_settled_gen.store(token, Ordering::Release);
}
}
#[inline]
pub(crate) fn notice_addr_flush_files(&self) {
self.addr_flush_gen.fetch_add(1, Ordering::AcqRel);
}
#[inline]
pub fn derive_temp_migration_path(&self) -> PathBuf {
let rand_id = fastrand::u128(..);
let b32 = encode_u128(rand_id);
let mut s = String::with_capacity(HASH_PREFIX_LEN + TREE_FILE_SUFFIX.len());
s.push_str(&b32);
s.push_str(TREE_FILE_SUFFIX);
self.migration_temp_dir.join(s)
}
pub fn dispose(&self) {
let pin = self.live_indexes.pin();
for entry in pin.values() {
if let Some(tree) = entry.tree.write().take() {
tree.dispose();
}
}
pin.clear();
}
pub fn dispose_bf_tree_deferred(&self, tree: Arc<BfTreeService>) {
if let Some(ref epoch) = self.store_epoch {
epoch.bump_current_epoch_action(move || {
tree.dispose();
});
} else {
tree.dispose();
}
}
#[inline]
pub fn locks(&self) -> &RangeIndexLocks {
&self.locks
}
#[inline]
pub fn key_id_of(key: &[u8]) -> u128 {
hash128(key, PREFIX_SEED_1, PREFIX_SEED_2)
}
#[inline]
pub fn base32_prefix_of(key: &[u8]) -> Base32Buf128 {
let id = Self::key_id_of(key);
encode_u128(id)
}
#[inline]
pub fn hash_prefix_of(key: &[u8]) -> String {
Self::base32_prefix_of(key).to_string()
}
#[inline]
pub fn key_hash_of(key: &[u8]) -> u64 {
fast_hash(key)
}
#[inline]
pub const fn round_up_to_power_of_2(v: u32) -> u32 {
v.next_power_of_two()
}
#[inline]
pub fn compute_leaf_page_size(max_record_size: usize) -> usize {
if max_record_size <= 2048 {
return 4096;
}
let target = ((max_record_size * 5 / 2).min(32768)) as u32;
Self::round_up_to_power_of_2(target) as usize
}
pub fn data_file_path(&self, hash_prefix: &str) -> PathBuf {
let mut file_name = String::with_capacity(hash_prefix.len() + DATA_FILE_SUFFIX.len());
file_name.push_str(hash_prefix);
file_name.push_str(DATA_FILE_SUFFIX);
self.ri_log_root.join(file_name)
}
#[inline]
pub fn data_file_path_by_id(&self, key_id: u128) -> PathBuf {
let b32 = encode_u128(key_id);
self.data_file_path(&b32)
}
#[inline]
pub fn data_file_path_for_key(&self, key: &[u8]) -> PathBuf {
self.data_file_path_by_id(Self::key_id_of(key))
}
pub fn log_flush_path(&self, hash_prefix: &str, logical_address: u64) -> PathBuf {
let b32 = encode_u64(logical_address);
let mut s =
String::with_capacity(hash_prefix.len() + 1 + BASE32_LEN_U64 + FLUSH_FILE_SUFFIX.len());
s.push_str(hash_prefix);
s.push('.');
s.push_str(&b32);
s.push_str(FLUSH_FILE_SUFFIX);
self.ri_log_root.join(s)
}
pub fn bare_flush_path(&self, hash_prefix: &str) -> PathBuf {
let mut flush_name = String::with_capacity(hash_prefix.len() + FLUSH_FILE_SUFFIX.len());
flush_name.push_str(hash_prefix);
flush_name.push_str(FLUSH_FILE_SUFFIX);
self.ri_log_root.join(flush_name)
}
#[inline]
pub fn live_indexes(&self) -> &GxPapayaMap<u128, Arc<TreeEntry>> {
&self.live_indexes
}
pub(crate) fn live_entries(&self) -> Vec<Arc<TreeEntry>> {
let pin = self.live_indexes.pin();
let mut entries = Vec::with_capacity(pin.len());
entries.extend(pin.values().cloned());
entries
}
#[inline]
pub fn get_tree(&self, key: &[u8]) -> Option<Arc<BfTreeService>> {
let key_id = Self::key_id_of(key);
let pin = self.live_indexes.pin();
pin
.get(&key_id)
.and_then(|e| e.tree.read().as_ref().cloned())
}
#[inline]
pub(crate) fn live_tree_of(&self, key_id: u128) -> Option<(Arc<TreeEntry>, Arc<BfTreeService>)> {
let pin = self.live_indexes.pin();
pin
.get(&key_id)
.and_then(|e| e.tree.read().as_ref().cloned().map(|t| (Arc::clone(e), t)))
}
pub fn token_snapshot_dir(target_dir: &Path, token: u128) -> PathBuf {
let b32 = encode_u128(token);
let mut p = target_dir.to_path_buf();
p.reserve(b32.len() + 1 + "rangeindex".len());
p.push(b32.as_str());
p.push("rangeindex");
p
}
#[inline]
pub fn checkpoint_snapshot_dir(&self, token: u128) -> PathBuf {
Self::token_snapshot_dir(&self.cpr_dir, token)
}
pub fn checkpoint_snapshot_path(&self, token: u128, hash_prefix: &str) -> PathBuf {
let mut p = self.checkpoint_snapshot_dir(token);
let mut file_name = String::with_capacity(hash_prefix.len() + TREE_FILE_SUFFIX.len());
file_name.push_str(hash_prefix);
file_name.push_str(TREE_FILE_SUFFIX);
p.push(file_name);
p
}
#[inline]
pub fn live_index_count(&self) -> usize {
self.live_indexes.pin().len()
}
#[inline]
pub fn is_checkpoint_in_progress(&self) -> bool {
self.checkpoint_in_progress.load(Ordering::Acquire)
}
}
impl Drop for RangeIndexManager {
fn drop(&mut self) {
self.dispose();
}
}