#[cfg(all(feature = "reactor", feature = "blocking"))]
compile_error!(
"features `reactor` and `blocking` are mutually exclusive: \
`reactor` enables btree-ondisk/arc, `blocking` enables \
btree-ondisk/rc. Pick one."
);
pub mod config;
pub mod meta_format;
pub mod inode;
pub mod ondisk;
pub mod segment;
pub mod block_source;
pub mod file;
pub mod s3uri;
pub mod buffer;
pub mod utils;
pub mod staging;
pub mod meta_loader;
pub mod data_cache;
pub(crate) mod s3commons;
pub use crate::s3commons::{s3_error_kind, s3_code_kind, is_condition_failure, is_retryable};
pub(crate) mod segment_body;
#[cfg(feature = "wal")]
pub mod wal;
pub mod node_cache;
pub type BlockIndex = u64;
pub type BlockPtr = u64;
pub type BlockOffset = usize;
pub type Cno = u64;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct SegmentId {
seq_id: u32,
part_id: Option<u16>,
}
pub type SegmentOffset = usize;
impl SegmentId {
#[inline]
pub const fn new(seq_id: u32) -> Self {
Self { seq_id, part_id: None }
}
#[inline]
pub const fn with_part(seq_id: u32, part_id: u16) -> Self {
Self { seq_id, part_id: Some(part_id) }
}
#[inline]
pub const fn seq_id(&self) -> u32 {
self.seq_id
}
#[inline]
pub const fn part_id(&self) -> Option<u16> {
self.part_id
}
#[inline]
pub const fn is_partial(&self) -> bool {
match self.part_id {
None => false,
Some(p) => p >= crate::segment::Segment::FIRST_DATA_PART,
}
}
#[inline]
pub const fn as_cno(&self) -> Cno {
self.seq_id as u64
}
#[inline]
pub const fn checkpoint(&self) -> Self {
Self::new(self.seq_id)
}
#[inline]
pub fn new_from_cno(cno: Cno) -> Self {
Self::from(cno)
}
#[inline]
pub const fn at_part(&self, part_id: u16) -> Self {
Self::with_part(self.seq_id, part_id)
}
#[inline]
pub const fn next(&self) -> Self {
Self::new(self.seq_id + 1)
}
}
impl From<Cno> for SegmentId {
#[inline]
fn from(cno: Cno) -> Self {
debug_assert!(cno <= u32::MAX as Cno, "checkpoint {} is past what a pointer can name", cno);
Self::new(cno as u32)
}
}
impl From<u32> for SegmentId {
#[inline]
fn from(seq_id: u32) -> Self {
Self::new(seq_id)
}
}
impl std::fmt::Display for SegmentId {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self.part_id {
None => f.pad(&self.seq_id.to_string()),
Some(p) => f.pad(&format!("{}.{}", self.seq_id, p)),
}
}
}
#[cfg(test)]
mod segment_id_tests {
use super::*;
#[test]
fn whole_and_part_round_trip() {
let w = SegmentId::new(12345);
assert_eq!(w.seq_id(), 12345);
assert_eq!(w.part_id(), None);
assert!(!w.is_partial(), "a checkpoint written as one object is not a partial");
for part in [0u16, 1, 255, 16383] {
let o = SegmentId::with_part(12345, part);
assert_eq!(o.seq_id(), 12345);
assert_eq!(o.part_id(), Some(part));
assert_eq!(o.is_partial(), part >= crate::segment::Segment::FIRST_DATA_PART,
"part {} classified wrongly", part);
}
}
#[test]
fn unparted_is_not_part_zero() {
assert_ne!(SegmentId::new(7), SegmentId::with_part(7, 0));
}
#[test]
fn ordering_is_checkpoint_major() {
assert!(SegmentId::with_part(5, 16383) < SegmentId::new(6));
assert!(SegmentId::new(5) < SegmentId::with_part(5, 0));
assert!(SegmentId::with_part(5, 1) < SegmentId::with_part(5, 2));
}
#[test]
fn part_zero_is_not_a_partial() {
assert!(!SegmentId::with_part(3, 0).is_partial());
assert!(SegmentId::with_part(3, 1).is_partial());
assert!(SegmentId::with_part(3, 9).is_partial());
}
#[test]
fn display_matches_the_object_naming() {
assert_eq!(SegmentId::new(3).to_string(), "3");
assert_eq!(SegmentId::with_part(3, 0).to_string(), "3.0");
assert_eq!(SegmentId::with_part(3, 12).to_string(), "3.12");
assert_eq!(format!("[{:<6}]", SegmentId::new(3)), "[3 ]");
assert_eq!(format!("[{:>6}]", SegmentId::with_part(3, 12)), "[ 3.12]");
assert_eq!(format!("[{:^7}]", SegmentId::with_part(3, 0)), "[ 3.0 ]");
assert_eq!(format!("[{:*>6}]", SegmentId::new(42)), "[****42]");
}
}
pub struct BlockIndexIter {
remain: usize,
block_size: usize,
current: usize, }
impl BlockIndexIter {
pub fn new(off: usize, len: usize, block_size: usize) -> Self {
Self {
remain: len,
block_size: block_size,
current: off,
}
}
}
impl Iterator for BlockIndexIter {
type Item = (BlockIndex, BlockOffset, usize);
fn next(&mut self) -> Option<Self::Item> {
if self.remain > 0 {
let blk_idx = (self.current / self.block_size) as BlockIndex;
let start = self.current;
let next_blk_idx = ((self.current + self.remain) / self.block_size) as BlockIndex;
let len = if next_blk_idx > blk_idx {
self.block_size - self.current % self.block_size
} else {
self.remain
};
self.current = start + len;
self.remain -= len;
Some((blk_idx, start % self.block_size, len))
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
if self.remain == 0 {
return (0, Some(0));
}
let first = self.current / self.block_size;
let last = (self.current + self.remain - 1) / self.block_size;
let n = last - first + 1;
(n, Some(n))
}
}
#[repr(C)]
pub struct BMapUserData {
pub blk_ptr_format: meta_format::BlockPtrFormat,
pad1: u8,
pad2: u8,
pad3: u8,
}
impl BMapUserData {
pub fn new(blk_ptr_format: meta_format::BlockPtrFormat) -> Self {
Self {
blk_ptr_format,
pad1: 0,
pad2: 0,
pad3: 0,
}
}
pub fn from_u32(user_data: u32) -> Self {
let mut bmap_user_data = unsafe { std::mem::MaybeUninit::zeroed().assume_init() };
let ptr = std::ptr::addr_of_mut!(bmap_user_data) as *mut u32;
unsafe {
std::ptr::write_volatile(ptr, user_data);
}
bmap_user_data
}
pub fn as_u32(&self) -> u32 {
let ptr = std::ptr::addr_of!(*self) as *const u32;
unsafe {
*ptr
}
}
}
pub(crate) const DEFAULT_FORWARD_ORIGIN_CONCURRENCY: usize = 10;
pub(crate) const DEFAULT_FORWARD_ORIGIN_THRESHOLD: usize = 8 * 1024 * 1024;
pub(crate) const DEFAULT_FORWARD_ORIGIN_CHUNK_SIZE: usize = 8 * 1024 * 1024;
pub(crate) const DEFAULT_READ_GET_MAX_BYTES: usize = 16 * 1024 * 1024;
pub(crate) const DEFAULT_READ_MAX_CONCURRENCY: usize = 10;
pub(crate) const DEFAULT_SEGMENT_BUFFER_SIZE: usize = 100 * 1024 * 1024;
pub(crate) const DEFAULT_MIDDLE_SEGMENT_BUFFER_SIZE: usize = 256 * 1024 * 1024;
pub(crate) const DEFAULT_LARGE_SEGMENT_BUFFER_SIZE: usize = 1024 * 1024 * 1024;
pub(crate) const DEFAULT_SEGMENT_MPU_CHUNK_SIZE: usize = 16 * 1024 * 1024;
pub(crate) const DEFAULT_SEGMENT_MPU_MAX_CONCURRENCY: usize = 8;
pub(crate) const DEFAULT_SEGMENT_MAX_OBJECT_BYTES: usize = crate::meta_format::BLOCK_PTR_PARTED_MAX_PART_BYTES;
pub(crate) const MIN_SEGMENT_MAX_OBJECT_BYTES: usize = 16 * 1024;
pub(crate) const DEFAULT_DATA_CACHE_BLOCKS: usize = 1024;
pub(crate) const DEFAULT_MIDDLE_DATA_CACHE_BLOCKS: usize = 65536;
pub(crate) const DEFAULT_LARGE_DATA_CACHE_BLOCKS: usize = 256000;
pub(crate) const DEFAULT_MAX_DIRTY_DATA_BYTES_THRESHOLD: usize = 8_388_608;
pub(crate) const DEFAULT_MIDDLE_MAX_DIRTY_DATA_BYTES_THRESHOLD: usize = 256 * 1024 * 1024;
pub(crate) const DEFAULT_LARGE_MAX_DIRTY_DATA_BYTES_THRESHOLD: usize = 1024 * 1024 * 1024;
pub(crate) const DEFAULT_MAX_DIRTY_DATA_BLOCKS_THRESHOLD: usize = 32;
pub(crate) const DEFAULT_MIDDLE_MAX_DIRTY_DATA_BLOCKS_THRESHOLD: usize = 65536;
pub(crate) const DEFAULT_LARGE_MAX_DIRTY_DATA_BLOCKS_THRESHOLD: usize = 256000;
pub(crate) const DEFAULT_MAX_DIRTY_DATA_FLUSH_INTERVAL: u64 = 5000;
pub(crate) const DEFAULT_FLUSH_RETRIES: usize = 3;
pub(crate) const DEFAULT_FLUSH_BACKOFF_SECS: u64 = 1;
pub(crate) const DEFAULT_PARTIAL_FLUSH_TIMEOUT: u64 = 5;
pub(crate) const DEFAULT_PARTIAL_FLUSH_CHECK_INTERVAL_SECS: u64 = 1;
pub(crate) const DEFAULT_NODE_CACHE_BLOCKS: usize = 1024;
pub(crate) const DEFAULT_MAX_NODE_CACHE_BLOCKS: usize = btree_ondisk::DEFAULT_CACHE_UNLIMITED;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn block_index_iter_zero_length() {
let iter = BlockIndexIter::new(0, 0, 4096);
let items: Vec<_> = iter.collect();
assert!(items.is_empty());
}
#[test]
fn block_index_iter_within_single_block() {
let items: Vec<_> = BlockIndexIter::new(10, 100, 4096).collect();
assert_eq!(items, vec![(0, 10, 100)]);
}
#[test]
fn block_index_iter_exact_block_aligned() {
let items: Vec<_> = BlockIndexIter::new(0, 4096, 4096).collect();
assert_eq!(items, vec![(0, 0, 4096)]);
}
#[test]
fn block_index_iter_cross_two_blocks() {
let items: Vec<_> = BlockIndexIter::new(4090, 100, 4096).collect();
assert_eq!(items, vec![(0, 4090, 6), (1, 0, 94)]);
}
#[test]
fn block_index_iter_cross_three_blocks() {
let items: Vec<_> = BlockIndexIter::new(4000, 4096 + 96 + 96, 4096).collect();
assert_eq!(items, vec![(0, 4000, 96), (1, 0, 4096), (2, 0, 96)]);
}
#[test]
fn block_index_iter_starts_mid_block() {
let items: Vec<_> = BlockIndexIter::new(8192, 4096, 4096).collect();
assert_eq!(items, vec![(2, 0, 4096)]);
}
#[test]
fn block_index_iter_total_length_preserved() {
let off = 1234;
let len = 9999;
let total: usize = BlockIndexIter::new(off, len, 4096).map(|(_, _, l)| l).sum();
assert_eq!(total, len);
}
#[test]
fn bmap_user_data_round_trip() {
let ud = BMapUserData::new(meta_format::BlockPtrFormat::MicroGroup);
let val = ud.as_u32();
let ud2 = BMapUserData::from_u32(val);
assert_eq!(ud2.blk_ptr_format, meta_format::BlockPtrFormat::MicroGroup);
assert_eq!(ud2.as_u32(), val);
}
#[test]
fn bmap_user_data_flat() {
let ud = BMapUserData::new(meta_format::BlockPtrFormat::Flat);
let val = ud.as_u32();
let ud2 = BMapUserData::from_u32(val);
assert_eq!(ud2.blk_ptr_format, meta_format::BlockPtrFormat::Flat);
}
#[test]
fn bmap_user_data_nop() {
let ud = BMapUserData::new(meta_format::BlockPtrFormat::Nop);
assert_eq!(ud.as_u32() & 0xFF, 0); }
}
#[cfg(test)]
mod build_matrix_tests {
const COVERED_BY_THE_MATRIX: &[&str] = &[
"reactor",
"meta_loader_batch",
"wal",
"range-lock",
"concurrent-segment-build",
"blocking",
"bench",
];
#[test]
fn every_declared_feature_is_built_somewhere() {
let manifest = include_str!("../Cargo.toml");
let mut declared = Vec::new();
let mut in_features = false;
for line in manifest.lines() {
let line = line.trim();
if line.starts_with('[') {
in_features = line == "[features]";
continue;
}
if !in_features || line.is_empty() || line.starts_with('#') {
continue;
}
if let Some((name, _)) = line.split_once('=') {
let name = name.trim();
if !name.is_empty() && name != "default" {
declared.push(name.to_string());
}
}
}
assert!(declared.len() >= 7,
"parsed only {:?} from [features], so this test is not reading the manifest \
it thinks it is", declared);
let missing: Vec<&String> = declared.iter()
.filter(|f| !COVERED_BY_THE_MATRIX.contains(&f.as_str()))
.collect();
assert!(missing.is_empty(),
"{:?} are declared features that the build matrix never turns on. A feature \
nothing builds is a feature nothing checks: add a row to the matrix in \
tests/README.md and name it in COVERED_BY_THE_MATRIX.", missing);
let stale: Vec<&&str> = COVERED_BY_THE_MATRIX.iter()
.filter(|f| !declared.iter().any(|d| d == *f))
.collect();
assert!(stale.is_empty(),
"{:?} are listed as covered but no longer declared in Cargo.toml", stale);
}
}