use crate::format::wal::{self, OpView};
use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::OnceLock;
const BASE_CAP: usize = 64;
const CHUNKS: usize = 20;
const CAPACITY: usize = BASE_CAP * ((1 << CHUNKS) - 1);
const OFF_BITS: u32 = 38;
const OFF_MASK: u64 = (1 << OFF_BITS) - 1;
fn locate(i: usize) -> (usize, usize) {
let x = i / BASE_CAP + 1;
let k = (usize::BITS - 1 - x.leading_zeros()) as usize;
let before = BASE_CAP * ((1 << k) - 1);
(k, i - before)
}
#[derive(Default)]
struct Entry {
seq: AtomicU64,
at: AtomicU64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct OpRef(u64);
type Chunks<T> = [OnceLock<Box<[T]>>; CHUNKS];
pub(crate) struct LogArena {
segs: Chunks<OnceLock<Vec<u8>>>,
entries: Chunks<Entry>,
nsegs: AtomicUsize,
frontier: AtomicUsize,
}
#[allow(clippy::missing_fields_in_debug)] impl std::fmt::Debug for LogArena {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LogArena")
.field("ops", &self.len())
.field("segments", &self.nsegs.load(Ordering::Relaxed))
.finish()
}
}
fn cell<T: Default>(chunks: &Chunks<T>, i: usize) -> Option<&T> {
let (k, off) = locate(i);
let chunk = chunks.get(k)?.get_or_init(|| {
(0..BASE_CAP << k).map(|_| T::default()).collect::<Vec<_>>().into_boxed_slice()
});
chunk.get(off)
}
impl LogArena {
pub(crate) fn new() -> Self {
Self {
segs: std::array::from_fn(|_| OnceLock::new()),
entries: std::array::from_fn(|_| OnceLock::new()),
nsegs: AtomicUsize::new(0),
frontier: AtomicUsize::new(0),
}
}
pub(crate) fn len(&self) -> usize {
self.frontier.load(Ordering::Acquire)
}
pub(crate) fn reserve(&self, ops: usize, bytes: usize) -> bool {
#[cfg(test)]
if crate::store::probe::full() {
return false;
}
let from = self.frontier.load(Ordering::Relaxed);
let seg = self.nsegs.load(Ordering::Relaxed);
let Some(end) = from.checked_add(ops) else { return false };
if end > CAPACITY || seg >= CAPACITY || bytes as u64 > OFF_MASK {
return false;
}
let _ = cell(&self.segs, seg);
let mut i = from;
while i < end {
let _ = cell(&self.entries, i);
i = BASE_CAP * ((1 << (locate(i).0 + 1)) - 1);
}
true
}
pub(crate) fn publish<'s, I>(&'s self, seg: Vec<u8>, ops: impl FnOnce(&'s [u8]) -> I)
where
I: Iterator<Item = (u64, usize)>,
{
let s = self.nsegs.load(Ordering::Relaxed);
let Some(slot) = cell(&self.segs, s) else { return };
let fresh = slot.set(seg).is_ok();
debug_assert!(fresh, "segment cell double-initialized");
self.nsegs.store(s + 1, Ordering::Relaxed);
let bytes = slot.get().map_or(&[][..], Vec::as_slice);
let mut i = self.frontier.load(Ordering::Relaxed);
for (seq, off) in ops(bytes) {
let Some(e) = cell(&self.entries, i) else { break };
e.seq.store(seq, Ordering::Relaxed);
e.at.store((s as u64) << OFF_BITS | off as u64, Ordering::Relaxed);
i += 1;
}
self.frontier.store(i, Ordering::Release);
}
fn entry(&self, i: usize) -> Option<(u64, OpRef)> {
let e = cell(&self.entries, i)?;
Some((e.seq.load(Ordering::Relaxed), OpRef(e.at.load(Ordering::Relaxed))))
}
pub(crate) fn last_seq(&self) -> u64 {
match self.len() {
0 => 0,
n => self.entry(n - 1).map_or(0, |e| e.0),
}
}
pub(crate) fn committed(&self, upto: usize) -> impl Iterator<Item = (u64, OpRef)> + '_ {
(0..upto).filter_map(move |i| self.entry(i))
}
pub(crate) fn view(&self, r: OpRef) -> Option<OpView<'_>> {
let seg = cell(&self.segs, (r.0 >> OFF_BITS) as usize)?.get()?;
let off = (r.0 & OFF_MASK) as usize;
let len = crate::bytesio::get_u32(seg.get(off..off + 4)?, 0) as usize;
OpView::trusted(seg.get(off..off + len)?)
}
#[inline]
pub(crate) fn take_first_segment(&mut self) -> Option<Vec<u8>> {
self.segs[0].get_mut()?.get_mut(0)?.take()
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct OverlayNode {
pub(crate) core: Option<OpRef>,
pub(crate) segs: Option<OpRef>,
pub(crate) edges: Option<OpRef>,
pub(crate) attrs: Option<OpRef>,
pub(crate) touch: Option<OpRef>,
pub(crate) carried: Option<(u16, u16, u64)>,
pub(crate) pinned: Option<bool>,
pub(crate) removed: bool,
pub(crate) severed: bool,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct KeyHasher(u64);
impl std::hash::Hasher for KeyHasher {
fn finish(&self) -> u64 {
crate::hash::fmix64(self.0)
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = self.0.rotate_left(8) ^ u64::from(b);
}
}
fn write_u64(&mut self, v: u64) {
self.0 ^= v;
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct KeyState(u64);
impl Default for KeyState {
#[inline]
fn default() -> Self {
use std::hash::BuildHasher as _;
Self(std::collections::hash_map::RandomState::new().hash_one(0x5EED_u64))
}
}
impl std::hash::BuildHasher for KeyState {
type Hasher = KeyHasher;
fn build_hasher(&self) -> KeyHasher {
KeyHasher(self.0)
}
}
pub(crate) type KeyMap<V> = HashMap<u64, V, KeyState>;
#[derive(Debug, Default)]
pub(crate) struct OverlayView {
pub(crate) map: KeyMap<OverlayNode>,
}
impl OverlayView {
pub(crate) fn build(log: &LogArena, upto: usize) -> Self {
const PINNED: u16 = crate::format::base::nflags::PINNED;
if upto == 0 {
return Self { map: KeyMap::with_hasher(KeyState(0)) };
}
let mut map: KeyMap<OverlayNode> = KeyMap::default();
for (_, r) in log.committed(upto) {
let Some(op) = log.view(r) else { continue };
let entry = map.entry(op.key().0).or_default();
match op {
OpView::Upsert(u) => {
let upsert_pin = u.flags() & PINNED != 0;
let prior_core_pin = entry
.core
.and_then(|c| log.view(c))
.is_some_and(|c| matches!(c, OpView::Upsert(c) if c.flags() & PINNED != 0));
let folded_pin = match entry.pinned {
Some(prev) => Some(upsert_pin || prev),
None if upsert_pin || prior_core_pin => Some(true),
None => None,
};
entry.removed = false;
entry.carried = match entry.touch.and_then(|t| log.view(t)) {
Some(OpView::Touch(t)) => {
Some((t.checks(), t.changes(), t.last_change_ms()))
}
_ => entry.carried,
};
entry.touch = None;
entry.core = Some(r);
entry.pinned = folded_pin;
}
OpView::Segs(_) => entry.segs = Some(r),
OpView::Edges(_) => entry.edges = Some(r),
OpView::Attrs(_) => entry.attrs = Some(r),
OpView::Touch(_) => entry.touch = Some(r),
OpView::Remove(_) => {
*entry = OverlayNode { removed: true, severed: true, ..OverlayNode::default() };
}
OpView::Pin(_, pinned) => entry.pinned = Some(pinned),
}
}
Self { map }
}
}
pub(crate) fn publish_records(log: &LogArena, seg: Vec<u8>, from: usize, above: u64) {
log.publish(seg, move |s| {
wal::Records::trusted(s, from, s.len())
.flat_map(|r| r.ops())
.filter(move |&(seq, _, _)| seq > above)
.map(|(seq, off, _)| (seq, off))
});
}
#[cfg(test)]
mod tests {
use super::*;
use crate::format::wal::RecordBuf;
use crate::key::UrlKey;
use crate::writer::DocRecord;
fn removes(first: u64, n: u64) -> Vec<u8> {
let mut b = RecordBuf::new();
for k in 0..n {
b.push_remove(UrlKey(k));
}
b.seal(first).unwrap();
b.into_vec()
}
fn publish(a: &LogArena, rec: Vec<u8>) {
let ops = wal::Records::trusted(&rec, 0, rec.len()).map(|r| r.op_count() as usize).sum();
assert!(a.reserve(ops, rec.len()));
publish_records(a, rec, 0, 0);
}
#[test]
fn locate_walks_chunk_boundaries() {
assert_eq!(locate(0), (0, 0));
assert_eq!(locate(63), (0, 63));
assert_eq!(locate(64), (1, 0));
assert_eq!(locate(191), (1, 127));
assert_eq!(locate(192), (2, 0));
}
#[test]
fn publish_then_read_below_frontier() {
let a = LogArena::new();
for i in 0..200u64 {
publish(&a, removes(i + 1, 1));
}
assert_eq!(a.len(), 200);
assert_eq!(a.last_seq(), 200);
let seen: Vec<u64> = a.committed(200).map(|(s, _)| s).collect();
assert_eq!(seen, (1..=200).collect::<Vec<_>>());
let keys: Vec<u64> = a.committed(200).map(|(_, r)| a.view(r).unwrap().key().0).collect();
assert_eq!(keys, vec![0; 200], "each op resolves in place");
}
#[test]
fn a_reader_never_sees_part_of_a_record() {
const N: u64 = 7;
const RECORDS: u64 = 2_000;
let a = LogArena::new();
let done = std::sync::atomic::AtomicBool::new(false);
std::thread::scope(|s| {
s.spawn(|| {
let mut polls = 0u64;
while !done.load(Ordering::Acquire) || polls == 0 {
let len = a.len();
assert_eq!(len % N as usize, 0, "saw {len} ops: part of a record");
polls += 1;
}
});
for c in 0..RECORDS {
publish(&a, removes(c * N + 1, N));
}
done.store(true, Ordering::Release);
});
assert_eq!(a.len(), (N * RECORDS) as usize);
}
#[test]
fn reserve_refuses_past_capacity() {
let a = LogArena::new();
assert!(!a.reserve(CAPACITY + 1, 64));
assert!(!a.reserve(usize::MAX, 64));
assert!(!a.reserve(1, OFF_MASK as usize + 1), "a segment past the offset range");
assert!(a.reserve(1, 64));
assert_eq!(a.len(), 0, "reserving publishes nothing");
}
#[test]
fn view_folds_last_writer_wins_and_remove_clears() {
let key = UrlKey::of("https://x.dev/a");
let mut b = RecordBuf::new();
b.push_pin(key, true);
b.push_remove(key);
let doc = DocRecord {
url: "https://x.dev/a",
url_key: key,
content_hash: 9,
fetched_at_ms: 10,
title: None,
snippet: None,
etag: None,
pinned: false,
};
b.push_upsert(&doc, 0).unwrap();
b.seal(1).unwrap();
let a = LogArena::new();
publish(&a, b.into_vec());
let view = OverlayView::build(&a, a.len());
let n = &view.map[&key.0];
assert!(!n.removed, "upsert resurrects");
assert!(n.pinned.is_none(), "remove cleared the earlier pin");
let Some(OpView::Upsert(core)) = n.core.and_then(|r| a.view(r)) else { panic!("core") };
assert_eq!(core.content_hash(), 9);
}
}