#![allow(unsafe_code)]
use crate::{DbError, DbResult};
use crate::index::ShardIndex;
use memmap2::{MmapMut, MmapOptions};
use rayon::prelude::*;
use std::fs::OpenOptions;
use std::path::Path;
use std::sync::atomic::{AtomicU64, Ordering};
pub(crate) const CMAG: u32 = 0xC047_C047;
pub(crate) const CTOMB: u32 = 0xDEAD_C047;
pub(crate) const CHDR: usize = 24;
pub struct CommutativeLog {
mmap: MmapMut,
pub write_offset: AtomicU64,
committed_offset: AtomicU64,
pub capacity: u64,
pub(crate) commit_lock: parking_lot::Mutex<()>,
pub(crate) inflight: std::sync::atomic::AtomicI64,
}
impl CommutativeLog {
pub fn open(path: &Path, size: u64) -> DbResult<Self> {
use std::os::unix::io::AsRawFd;
let f = OpenOptions::new().read(true).write(true).create(true).truncate(false).open(path)?;
f.set_len(size)?;
let rc = unsafe { libc::posix_fallocate(f.as_raw_fd(), 0, size as libc::off_t) };
if rc != 0 {
return Err(DbError::Io(std::io::Error::from_raw_os_error(rc)));
}
let mmap = unsafe { MmapOptions::new().map_mut(&f)? };
let persisted = u64::from_le_bytes(mmap[0..8].try_into().unwrap_or([0u8; 8]));
let start = if persisted > 8 && persisted <= size { persisted } else { 8 };
Ok(Self {
mmap,
write_offset: AtomicU64::new(start),
committed_offset: AtomicU64::new(start),
capacity: size,
commit_lock: parking_lot::Mutex::new(()),
inflight: std::sync::atomic::AtomicI64::new(0),
})
}
#[inline(always)]
pub fn put_versioned(&self, key: [u8; 32], value: &[u8], prev_off: u64, height: u64) -> DbResult<u64> {
let total = (CHDR + 32 + value.len()) as u64;
let off = self.write_offset.fetch_add(total, Ordering::AcqRel);
if off + total > self.capacity {
self.write_offset.fetch_sub(total, Ordering::AcqRel);
return Err(DbError::LogFull);
}
self.inflight.fetch_add(1, Ordering::Relaxed);
#[repr(C, packed)]
struct Hdr { magic: u32, vlen: u32, prev: u64, height: u64 }
unsafe {
let base = self.mmap.as_ptr().add(off as usize) as *mut u8;
std::ptr::write_unaligned(base as *mut Hdr, Hdr {
magic: CMAG.to_le(),
vlen: (value.len() as u32).to_le(),
prev: prev_off.to_le(),
height: height.to_le(),
});
std::ptr::copy_nonoverlapping(key.as_ptr(), base.add(CHDR), 32);
std::ptr::copy_nonoverlapping(value.as_ptr(), base.add(CHDR + 32), value.len());
}
self.inflight.fetch_sub(1, Ordering::Release);
Ok(off)
}
#[inline(always)]
pub fn del_versioned(&self, key: [u8; 32], prev_off: u64, height: u64) -> DbResult<u64> {
let total = (CHDR + 32) as u64;
let off = self.write_offset.fetch_add(total, Ordering::AcqRel);
if off + total > self.capacity {
self.write_offset.fetch_sub(total, Ordering::AcqRel);
return Err(crate::DbError::LogFull);
}
self.inflight.fetch_add(1, Ordering::AcqRel);
unsafe {
#[repr(C, packed)]
struct Hdr { magic: u32, vlen: u32, prev: u64, height: u64 }
let base = self.mmap.as_ptr().add(off as usize) as *mut u8;
let hdr = Hdr {
magic: CTOMB.to_le(),
vlen: 0u32.to_le(),
prev: prev_off.to_le(),
height: height.to_le(),
};
std::ptr::write_unaligned(base as *mut Hdr, hdr);
std::ptr::copy_nonoverlapping(key.as_ptr(), base.add(CHDR), 32);
}
self.inflight.fetch_sub(1, Ordering::Release);
Ok(off)
}
pub fn commit_fold(
&self,
index: &ShardIndex,
prev_acc: [u8; 32],
) -> DbResult<([u8; 32], [u8; 32])> {
self.commit_fold_until(index, prev_acc, self.write_offset.load(Ordering::Acquire), 0)
}
pub fn commit_fold_until(
&self,
index: &ShardIndex,
prev_acc: [u8; 32],
end_offset: u64,
log_id: u64,
) -> DbResult<([u8; 32], [u8; 32])> {
let _guard = self.commit_lock.lock();
let start = self.committed_offset.load(Ordering::Acquire) as usize;
let end = end_offset as usize;
if start >= end {
return Ok((prev_acc, *blake3::hash(&prev_acc).as_bytes()));
}
while self.inflight.load(Ordering::Acquire) > 0 { std::hint::spin_loop(); }
let mmap_ptr = self.mmap.as_ptr() as usize;
let dirty_bytes = end.saturating_sub(start);
let estimated = (dirty_bytes / (CHDR + 32 + 64)).max(64);
let mut records: Vec<(usize, [u8; 32], usize)> = Vec::with_capacity(estimated);
let mut tombstones: Vec<(usize, [u8; 32])> = Vec::new();
let mut cur = start;
while cur + CHDR + 32 <= end {
let slice = &self.mmap[cur..];
let magic = u32::from_le_bytes(slice[0..4].try_into().unwrap());
if magic == CTOMB {
let key: [u8; 32] = slice[CHDR..CHDR + 32].try_into().unwrap();
tombstones.push((cur, key));
cur += CHDR + 32;
continue;
}
if magic != CMAG { break; }
let vlen = u32::from_le_bytes(slice[4..8].try_into().unwrap()) as usize;
let total = CHDR + 32 + vlen;
if cur + total > end { break; }
let key: [u8; 32] = slice[CHDR..CHDR + 32].try_into().unwrap();
records.push((cur, key, vlen));
cur += total;
}
let mut hashed: Vec<([u8; 32], [u8; 32], u64)> = records
.par_iter()
.map(|&(off, key, vlen)| {
let val = unsafe {
std::slice::from_raw_parts(
(mmap_ptr + off + CHDR + 32) as *const u8,
vlen,
)
};
(key, *blake3::hash(val).as_bytes(), off as u64)
})
.collect();
hashed.sort_unstable_by_key(|&(_, _, off)| off);
let mut per_block_count: ahash::AHashMap<[u8; 32], u32> =
ahash::AHashMap::with_capacity(hashed.len());
for &(key, _, _) in &hashed { *per_block_count.entry(key).or_insert(0) += 1; }
let mut latest: ahash::AHashMap<[u8; 32], ([u8; 32], u64)> =
ahash::AHashMap::with_capacity(hashed.len());
for (key, vh, off) in hashed {
latest.insert(key, (vh, off));
}
type ShardBatchEntry = (
[u8; 32], [u8; 32], u64, u32, bool, );
let mut shard_batches: [Vec<ShardBatchEntry>; 256] =
std::array::from_fn(|_| Vec::new());
let mut new_acc = prev_acc;
for &(off, key) in &tombstones {
if let Some(old_entry) = index.get_entry(&key) {
if !old_entry.deleted {
for i in 0..32 { new_acc[i] ^= key[i] ^ old_entry.value_hash[i]; }
}
}
let wc = index.count(&key) + 1;
shard_batches[key[0] as usize].push((key, [0u8; 32], off as u64, wc, true));
}
for (&key, &(new_vh, off)) in &latest {
let mut prev_offset = 0u64;
if let Some(old_entry) = index.get_entry(&key) {
if !old_entry.deleted {
for i in 0..32 { new_acc[i] ^= key[i] ^ old_entry.value_hash[i]; }
}
prev_offset = old_entry.offset;
}
for i in 0..32 { new_acc[i] ^= key[i] ^ new_vh[i]; }
if prev_offset > 0 {
unsafe {
let dst = (mmap_ptr + off as usize + 8) as *mut u8;
std::ptr::copy_nonoverlapping(prev_offset.to_le_bytes().as_ptr(), dst, 8);
}
}
let block_puts = per_block_count.get(&key).copied().unwrap_or(1);
let wc = index.count(&key) + block_puts;
let is_deleted = tombstones.iter().any(|(_, k)| k == &key);
shard_batches[key[0] as usize].push((key, new_vh, off, wc, is_deleted));
}
shard_batches.par_iter_mut().enumerate().for_each(|(shard_idx, batch)| {
if !batch.is_empty() {
let shard = &index.shards[shard_idx];
for &(key, vh, off, wc, deleted) in batch.iter() {
shard.upsert(key, off, wc, log_id, deleted, vh);
}
}
});
let root = *blake3::hash(&new_acc).as_bytes();
self.committed_offset.store(end as u64, Ordering::Release);
unsafe {
let dst = self.mmap.as_ptr() as *mut u8;
let bytes = (end as u64).to_le_bytes();
std::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, 8);
}
let _ = self.mmap.flush_async();
Ok((new_acc, root))
}
pub fn flush(&self) { let _ = self.mmap.flush_async(); }
pub fn write_offset(&self) -> u64 { self.write_offset.load(Ordering::Acquire) }
pub fn committed_offset(&self) -> u64 { self.committed_offset.load(Ordering::Acquire) }
pub fn mmap_ptr(&self) -> *const u8 { self.mmap.as_ptr() }
pub fn reset(&self) {
while self.inflight.load(Ordering::Acquire) > 0 {
std::hint::spin_loop();
}
self.write_offset.store(8, Ordering::Release);
self.committed_offset.store(8, Ordering::Release);
unsafe { std::ptr::write_bytes(self.mmap.as_ptr() as *mut u8, 0, 8); }
}
pub fn set_committed(&self, off: u64) {
self.committed_offset.store(off, Ordering::Release);
let woff = self.write_offset.load(Ordering::Acquire);
if off > woff { self.write_offset.store(off, Ordering::Release); }
}
pub(crate) fn scan_unflushed(&self, key: &[u8; 32]) -> Option<Vec<u8>> {
let committed = self.committed_offset.load(Ordering::Acquire) as usize;
let written = self.write_offset.load(Ordering::Acquire) as usize;
if written <= committed { return None; }
let cap = self.capacity as usize;
let ptr = self.mmap.as_ptr();
let mut best: Option<Vec<u8>> = None;
let mut cur = committed;
while cur + CHDR + 32 <= written {
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(cur), 4)
}.try_into().unwrap());
if magic == CTOMB {
let rec_key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap();
if rec_key == *key { best = None; } 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()) as usize;
let total = CHDR + 32 + vlen;
if cur + total > written || cur + total > cap { break; }
let rec_key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap();
if rec_key == *key {
best = Some(unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR + 32), vlen).to_vec()
});
}
cur += total;
}
best
}
pub(crate) fn scan_all_unflushed(&self) -> Vec<([u8; 32], Vec<u8>)> {
let committed = self.committed_offset.load(Ordering::Acquire) as usize;
let written = self.write_offset.load(Ordering::Acquire) as usize;
if written <= committed { return Vec::new(); }
let cap = self.capacity as usize;
let ptr = self.mmap.as_ptr();
let mut map: ahash::AHashMap<[u8; 32], Vec<u8>> = ahash::AHashMap::new();
let mut cur = committed;
while cur + CHDR + 32 <= written {
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(cur), 4)
}.try_into().unwrap());
if magic == CTOMB {
let key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap();
map.insert(key, Vec::new()); 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()) as usize;
let total = CHDR + 32 + vlen;
if cur + total > written || cur + total > cap { break; }
let key: [u8; 32] = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR), 32)
}.try_into().unwrap();
let val = unsafe {
std::slice::from_raw_parts(ptr.add(cur + CHDR + 32), vlen).to_vec()
};
map.insert(key, val);
cur += total;
}
map.into_iter().collect()
}
}
pub struct MmapRef {
_log: Option<std::sync::Arc<CommutativeLog>>,
ptr: *const u8,
len: usize,
_heap: Option<Vec<u8>>,
}
unsafe impl Send for MmapRef {}
unsafe impl Sync for MmapRef {}
impl MmapRef {
pub fn from_vec(v: Vec<u8>) -> Self {
let ptr = v.as_ptr();
let len = v.len();
MmapRef { _log: None, ptr, len, _heap: Some(v) }
}
}
impl std::ops::Deref for MmapRef {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
}
}
impl AsRef<[u8]> for MmapRef {
fn as_ref(&self) -> &[u8] { self }
}
impl std::fmt::Debug for MmapRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MmapRef({} bytes)", self.len)
}
}
impl CommutativeLog {
pub fn value_ref(self: &std::sync::Arc<Self>, off: usize) -> Option<MmapRef> {
let cap = self.capacity as usize;
if off + CHDR + 32 > cap { return None; }
let ptr = self.mmap.as_ptr();
let magic = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off), 4)
}.try_into().ok()?);
if magic != CMAG { return None; }
let vlen = u32::from_le_bytes(unsafe {
std::slice::from_raw_parts(ptr.add(off + 4), 4)
}.try_into().ok()?) as usize;
let vs = off + CHDR + 32;
if vs + vlen > cap { return None; }
Some(MmapRef {
_log: Some(std::sync::Arc::clone(self)),
ptr: unsafe { ptr.add(vs) },
len: vlen,
_heap: None,
})
}
}