use crate::commutative::CommutativeLog;
use crate::index::ShardIndex;
use crate::value_cache::ValueCache;
use arc_swap::ArcSwap;
use bytes::Bytes;
use std::path::{Path, PathBuf};
use std::sync::{Arc, mpsc};
use std::sync::atomic::{AtomicBool, Ordering};
pub struct WriteShard {
pub log: Arc<ArcSwap<CommutativeLog>>,
#[allow(dead_code)]
shard_id: usize,
}
impl WriteShard {
fn new(shard_id: usize, initial: Arc<CommutativeLog>) -> Self {
Self {
log: Arc::new(ArcSwap::from(initial)),
shard_id,
}
}
fn snap(&self) -> (Arc<CommutativeLog>, u64) {
let log = self.log.load_full();
let end_off = log.write_offset();
(log, end_off)
}
}
pub struct BlockWriter {
pub log: Arc<CommutativeLog>,
pub height: u64,
}
impl BlockWriter {
#[inline(always)]
pub fn put(&self, key: [u8; 32], value: &[u8]) -> crate::DbResult<u64> {
self.log.put_versioned(key, value, 0, self.height)
}
}
struct FoldReq {
shards: Vec<(Arc<CommutativeLog>, u64)>,
log_id: u64,
acc: [u8; 32],
ack: mpsc::Sender<[u8; 32]>,
cache: ValueCache,
}
struct FoldThread {
_h: std::thread::JoinHandle<()>,
stop: Arc<AtomicBool>,
}
impl Drop for FoldThread {
fn drop(&mut self) {
self.stop.store(true, Ordering::Release);
}
}
#[must_use = "call .wait() to block until fold completes, or .into_rx() to integrate with async code"]
pub struct FoldAck(mpsc::Receiver<[u8; 32]>);
impl FoldAck {
#[allow(dead_code)]
pub fn wait(self) -> [u8; 32] {
self.0.recv().unwrap_or([0u8; 32])
}
pub fn into_rx(self) -> mpsc::Receiver<[u8; 32]> {
self.0
}
}
pub struct DualBufferEngine {
pub shards: Vec<WriteShard>,
pub active: Arc<ArcSwap<CommutativeLog>>,
pub committed: Arc<ArcSwap<CommutativeLog>>,
fold_tx: mpsc::SyncSender<FoldReq>,
pub index: Arc<ShardIndex>,
pub merkle: Arc<parking_lot::Mutex<[u8; 32]>>,
#[allow(dead_code)]
dir: PathBuf,
#[allow(dead_code)]
buf_size: u64,
_fold: FoldThread,
pub vcache: ValueCache,
}
impl DualBufferEngine {
pub fn open(
dir: &Path,
buf_size: u64,
active_log: Arc<CommutativeLog>,
) -> crate::DbResult<Self> {
std::fs::create_dir_all(dir).map_err(crate::DbError::Io)?;
let index = Arc::new(ShardIndex::new());
let merkle = Arc::new(parking_lot::Mutex::new([0u8; 32]));
let stop = Arc::new(AtomicBool::new(false));
let vcache = ValueCache::new(200_000);
let n_shards = num_cpus::get().max(2);
let mut shards = Vec::with_capacity(n_shards);
for i in 0..n_shards {
let init = if i == 0 {
Arc::clone(&active_log)
} else {
let path = dir.join(format!("shard_{i}_active.log"));
Arc::new(CommutativeLog::open(&path, buf_size)?)
};
shards.push(WriteShard::new(i, init));
}
let active = Arc::clone(&shards[0].log);
let committed = Arc::new(ArcSwap::from(Arc::clone(&active_log)));
let (fold_tx, fold_rx) = mpsc::sync_channel::<FoldReq>(2);
let idx2 = Arc::clone(&index);
let mrk2 = Arc::clone(&merkle);
let stp2 = Arc::clone(&stop);
let fold_h = std::thread::Builder::new()
.name("dbx-fold".into())
.spawn(move || {
if let Some(cores) = core_affinity::get_core_ids() {
let c = if cores.len() > 1 { cores[1] } else { cores[0] };
core_affinity::set_for_current(c);
}
let fold_threads = (num_cpus::get() / 2).max(2);
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(fold_threads)
.thread_name(|i| format!("dbx-fold-worker-{i}"))
.build()
.unwrap_or_else(|_| {
rayon::ThreadPoolBuilder::new()
.num_threads(2)
.build()
.unwrap()
});
loop {
if stp2.load(Ordering::Acquire) { break; }
match fold_rx.recv_timeout(std::time::Duration::from_millis(5)) {
Ok(req) => {
use rayon::prelude::*;
use crate::commutative::{CMAG, CTOMB, CHDR};
let partial_accs: Vec<[u8; 32]> = pool.install(|| {
req.shards.par_iter().map(|(log, end_off)| {
let start = log.committed_offset() as usize;
let shard_acc = match log.commit_fold_until(
&idx2, req.acc, *end_off, req.log_id,
) {
Ok((a, _)) => a,
Err(_) => req.acc,
};
let end = *end_off as usize;
let ptr = log.mmap_ptr();
let cap = log.capacity as usize;
let mut cur = start;
while cur + CHDR + 32 <= end.min(cap) {
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(cur), 4)
}.try_into().unwrap_or([0u8;4]));
if magic == CTOMB {
let key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap_or([0u8;32]);
req.cache.remove(&key);
cur += CHDR + 32;
continue;
}
if magic != CMAG { break; }
let vlen = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(cur + 4), 4)
}.try_into().unwrap_or([0u8;4])) as usize;
let total = CHDR + 32 + vlen;
if cur + total > end.min(cap) { break; }
let key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap_or([0u8;32]);
if vlen <= 8192 {
let val = Bytes::copy_from_slice(unsafe {
std::slice::from_raw_parts(
ptr.add(cur + CHDR + 32), vlen)
});
req.cache.insert(key, val);
}
cur += total;
}
shard_acc
}).collect()
});
let final_acc = if !partial_accs.is_empty() {
partial_accs[0]
} else {
req.acc
};
*mrk2.lock() = final_acc;
let _ = req.ack.send(final_acc);
}
Err(mpsc::RecvTimeoutError::Disconnected) => break,
Err(_) => {}
}
}
})
.unwrap();
Ok(Self {
shards,
active,
committed,
fold_tx,
index,
merkle,
dir: dir.to_owned(),
buf_size,
_fold: FoldThread { _h: fold_h, stop },
vcache,
})
}
pub fn writer(&self, shard_id: usize, height: u64) -> BlockWriter {
let shard = &self.shards[shard_id % self.shards.len()];
BlockWriter {
log: shard.log.load_full(),
height,
}
}
pub fn swap(&self, log_id: u64) -> crate::DbResult<FoldAck> {
let acc = *self.merkle.lock();
let (tx, rx) = mpsc::channel();
let mut shard_data = Vec::with_capacity(self.shards.len());
for shard in &self.shards {
let (log, end_off) = shard.snap();
while log.inflight.load(Ordering::Acquire) > 0 {
std::hint::spin_loop();
}
shard_data.push((log, end_off));
}
self.fold_tx
.send(FoldReq {
shards: shard_data,
log_id,
acc,
ack: tx,
cache: self.vcache.clone(),
})
.map_err(|_| {
crate::DbError::Io(std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"fold thread stopped",
))
})?;
Ok(FoldAck(rx))
}
pub fn replace_shard_log(&self, shard_id: usize, new_log: Arc<CommutativeLog>) {
let idx = shard_id % self.shards.len();
self.shards[idx].log.store(Arc::clone(&new_log));
if idx == 0 {
self.active.store(Arc::clone(&new_log));
self.committed.store(Arc::clone(&new_log));
}
}
pub fn num_shards(&self) -> usize {
self.shards.len()
}
pub fn state_root(&self) -> [u8; 32] {
*blake3::hash(&*self.merkle.lock()).as_bytes()
}
}