use std::cell::UnsafeCell;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::record::ReadView;
pub const INLINE_CAP: usize = 256;
enum ContentStorage {
Inline([u8; INLINE_CAP]),
Spill(Box<[u8]>),
}
#[repr(C)]
pub(crate) struct SlotInner {
pub(crate) record_id: u64,
pub(crate) timestamp_ns: u64,
pub(crate) content_len: u32,
storage: ContentStorage,
pub(crate) hash_n: [u8; 32],
}
pub struct Slot {
inner: UnsafeCell<SlotInner>,
sequence: AtomicU64,
}
unsafe impl Sync for Slot {}
impl Slot {
pub(crate) fn new() -> Self {
Self {
inner: UnsafeCell::new(SlotInner {
record_id: 0,
timestamp_ns: 0,
content_len: 0,
storage: ContentStorage::Inline([0u8; INLINE_CAP]),
hash_n: [0u8; 32],
}),
sequence: AtomicU64::new(0),
}
}
#[inline]
pub unsafe fn producer_write(&self, seq: u64, ts: u64, content: &[u8]) { unsafe {
let inner = &mut *self.inner.get();
inner.record_id = seq;
inner.timestamp_ns = ts;
inner.content_len = content.len() as u32;
if content.len() <= INLINE_CAP {
match &mut inner.storage {
ContentStorage::Inline(buf) => {
buf[..content.len()].copy_from_slice(content);
}
_ => {
let mut buf = [0u8; INLINE_CAP];
buf[..content.len()].copy_from_slice(content);
inner.storage = ContentStorage::Inline(buf);
}
}
} else {
inner.storage = ContentStorage::Spill(content.to_vec().into_boxed_slice());
}
}}
#[inline]
pub fn publish(&self, seq: u64) {
self.sequence.store(seq + 1, Ordering::Release);
}
#[inline]
pub fn is_published(&self, seq: u64) -> bool {
self.sequence.load(Ordering::Acquire) == seq + 1
}
#[inline]
pub unsafe fn read(&self) -> ReadView<'_> { unsafe {
let inner = &*self.inner.get();
let content = match &inner.storage {
ContentStorage::Inline(buf) => &buf[..inner.content_len as usize],
ContentStorage::Spill(b) => &b[..inner.content_len as usize],
};
ReadView {
record_id: inner.record_id,
timestamp_ns: inner.timestamp_ns,
content,
hash_n: &inner.hash_n,
}
}}
#[inline]
pub unsafe fn write_hash(&self, hash_n: [u8; 32]) { unsafe {
(&mut *self.inner.get()).hash_n = hash_n;
}}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn slot_is_send_and_sync() {
fn assert_send<T: Send>() {}
fn assert_sync<T: Sync>() {}
assert_send::<Slot>();
assert_sync::<Slot>();
}
#[test]
fn inline_write_and_read() {
let slot = Slot::new();
let content = b"hello logdb";
unsafe {
slot.producer_write(42, 1000, content);
}
slot.publish(42);
assert!(slot.is_published(42));
assert!(!slot.is_published(43));
unsafe {
let view = slot.read();
assert_eq!(view.record_id, 42);
assert_eq!(view.timestamp_ns, 1000);
assert_eq!(view.content, content);
assert_eq!(view.hash_n, &[0u8; 32]);
}
}
#[test]
fn spill_write_and_read() {
let slot = Slot::new();
let content = vec![0xAAu8; 300];
unsafe {
slot.producer_write(7, 2000, &content);
}
slot.publish(7);
assert!(slot.is_published(7));
unsafe {
let view = slot.read();
assert_eq!(view.record_id, 7);
assert_eq!(view.timestamp_ns, 2000);
assert_eq!(view.content, &content[..]);
}
}
#[test]
fn switch_from_spill_to_inline() {
let slot = Slot::new();
let large = vec![0xBBu8; 300];
unsafe {
slot.producer_write(1, 100, &large);
}
slot.publish(1);
unsafe {
let view = slot.read();
assert_eq!(view.content.len(), 300);
}
let small = b"small";
unsafe {
slot.producer_write(2, 200, small);
}
slot.publish(2);
unsafe {
let view = slot.read();
assert_eq!(view.content, small);
}
}
#[test]
fn empty_slot_not_published() {
let slot = Slot::new();
assert!(!slot.is_published(0)); }
#[test]
fn write_hash_persists() {
let slot = Slot::new();
let hash = [0x42u8; 32];
unsafe {
slot.producer_write(10, 500, b"test");
slot.write_hash(hash);
}
slot.publish(10);
unsafe {
let view = slot.read();
assert_eq!(*view.hash_n, hash);
}
}
#[test]
fn zero_length_content() {
let slot = Slot::new();
unsafe {
slot.producer_write(0, 0, b"");
}
slot.publish(0);
unsafe {
let view = slot.read();
assert_eq!(view.content.len(), 0);
}
}
#[test]
fn exact_inline_boundary() {
let slot = Slot::new();
let content = vec![0xCCu8; INLINE_CAP];
unsafe {
slot.producer_write(5, 3000, &content);
}
slot.publish(5);
unsafe {
let view = slot.read();
assert_eq!(view.content.len(), INLINE_CAP);
assert_eq!(view.content, &content[..]);
}
}
#[test]
fn just_above_inline_boundary() {
let slot = Slot::new();
let content = vec![0xDDu8; INLINE_CAP + 1];
unsafe {
slot.producer_write(6, 4000, &content);
}
slot.publish(6);
unsafe {
let view = slot.read();
assert_eq!(view.content.len(), INLINE_CAP + 1);
assert_eq!(view.content, &content[..]);
}
}
#[test]
fn release_acquire_visibility() {
use std::sync::Arc;
use std::thread;
let slot = Arc::new(Slot::new());
let slot_clone = Arc::clone(&slot);
let handle = thread::spawn(move || {
unsafe {
slot_clone.producer_write(99, 9999, b"concurrent");
}
slot_clone.publish(99);
});
handle.join().unwrap();
assert!(slot.is_published(99));
unsafe {
let view = slot.read();
assert_eq!(view.record_id, 99);
assert_eq!(view.content, b"concurrent");
}
}
}