use std::{
hint::spin_loop,
slice::from_raw_parts_mut,
sync::atomic::{
AtomicU64,
Ordering::{AcqRel, Acquire, Release},
},
};
pub use addr::ABSOLUTE_ADDRESS_MASK;
pub use addr::READ_CACHE_BIT;
use itoa::Buffer;
use parking_lot::Mutex;
use wbase::{
addr::{self},
align::CachePadded,
};
use whlog::{CircularPageBuffer, HybridLogConfig};
use windex::HashIndex;
use wrecord::{HEADER_SIZE, RecordHeader, encode_to_slice};
use crate::error::{Error, Result};
#[inline(always)]
pub const fn is_read_cache_addr(addr: u64) -> bool {
addr::is_read_cache(addr)
}
#[inline(always)]
pub const fn absolute_address(addr: u64) -> u64 {
addr::to_absolute(addr)
}
#[inline(always)]
pub const fn tag_read_cache_addr(addr: u64) -> u64 {
addr::with_read_cache(addr)
}
pub struct ReadCache {
buffer: CircularPageBuffer,
pub page_size: usize,
pub num_pages: usize,
page_shift: u32,
page_mask: u64,
capacity: u64,
tail_address: CachePadded<AtomicU64>,
head_address: CachePadded<AtomicU64>,
turn_lock: Mutex<()>,
pub is_enabled: bool,
}
impl ReadCache {
pub fn new(page_size: usize, num_pages: usize, is_enabled: bool) -> Result<Self> {
if !page_size.is_power_of_two() || page_size == 0 {
let mut msg = String::from("ReadCache page_size 必须为非零且为 2 的幂,当前为 ");
let mut buf = Buffer::new();
msg.push_str(buf.format(page_size));
return Err(Error::InvalidConfig(msg));
}
if !num_pages.is_power_of_two() || num_pages == 0 {
let mut msg = String::from("ReadCache num_pages 必须为非零且为 2 的幂,当前为 ");
let mut buf = Buffer::new();
msg.push_str(buf.format(num_pages));
return Err(Error::InvalidConfig(msg));
}
let dummy_config = HybridLogConfig {
page_size,
num_pages,
mutable_fraction: 1.0,
ro_lag_num: whlog::ro_lag_num_from_fraction(1.0),
initial_address: 0,
};
let buffer = CircularPageBuffer::new(&dummy_config)?;
buffer.clear_page(0);
let page_shift = page_size.trailing_zeros();
let page_mask = (page_size - 1) as u64;
let capacity = (num_pages * page_size) as u64;
Ok(Self {
buffer,
page_size,
num_pages,
page_shift,
page_mask,
capacity,
tail_address: CachePadded(AtomicU64::new(0)),
head_address: CachePadded(AtomicU64::new(0)),
turn_lock: Mutex::new(()),
is_enabled,
})
}
#[inline(always)]
pub fn tail_address(&self) -> u64 {
self.tail_address.load(Acquire)
}
#[inline(always)]
pub fn head_address(&self) -> u64 {
self.head_address.load(Acquire)
}
pub fn append(
&self,
key: &[u8],
val: &[u8],
prev_main_addr: u64,
index: &HashIndex,
) -> Option<u64> {
if !self.is_enabled {
return None;
}
let rec_size = HEADER_SIZE.checked_add(key.len())?.checked_add(val.len())?;
if rec_size > self.page_size {
return None; }
loop {
let curr_tail = self.tail_address.load(Acquire);
let page_offset = curr_tail & self.page_mask;
let remaining = (self.page_size as u64) - page_offset;
if (rec_size as u64) <= remaining {
let new_tail = curr_tail + rec_size as u64;
if self
.tail_address
.compare_exchange_weak(curr_tail, new_tail, AcqRel, Acquire)
.is_ok()
{
let page_id = curr_tail >> self.page_shift;
let slot = self.buffer.page_idx(page_id);
unsafe {
let page_ptr = self.buffer.raw_page_ptr_mut(slot);
let dest = from_raw_parts_mut(page_ptr.add(page_offset as usize), rec_size);
encode_to_slice(dest, prev_main_addr, key, val, false).ok()?;
}
let min_head = new_tail.saturating_sub(self.capacity);
self.head_address.fetch_max(min_head, Release);
return Some(tag_read_cache_addr(curr_tail));
}
spin_loop();
} else {
let _lock = self.turn_lock.lock();
let curr_tail2 = self.tail_address.load(Acquire);
let page_offset2 = curr_tail2 & self.page_mask;
let remaining2 = (self.page_size as u64) - page_offset2;
if (rec_size as u64) > remaining2 {
let page_id = curr_tail2 >> self.page_shift;
if remaining2 >= (HEADER_SIZE as u64) {
let slot = self.buffer.page_idx(page_id);
unsafe {
let page_ptr = self.buffer.raw_page_ptr_mut(slot);
let pad_dest =
from_raw_parts_mut(page_ptr.add(page_offset2 as usize), remaining2 as usize);
let pad_header = RecordHeader::pad(remaining2 as usize);
pad_dest[..HEADER_SIZE].copy_from_slice(&pad_header.to_bytes());
}
}
let next_page_start = curr_tail2 + remaining2;
let next_page_id = next_page_start >> self.page_shift;
let min_head = next_page_start.saturating_sub(self.capacity);
self.head_address.fetch_max(min_head, Release);
if next_page_id >= self.num_pages as u64 {
let evicted_page_id = next_page_id - (self.num_pages as u64);
self.cleanse_page(evicted_page_id, index);
}
self.buffer.clear_page(next_page_id);
self.tail_address.store(next_page_start, Release);
}
}
}
}
fn cleanse_page(&self, page_id: u64, index: &HashIndex) {
let page_start_addr = page_id << self.page_shift;
let guard = self.buffer.read_page(page_id);
let mut offset = 0;
while offset + HEADER_SIZE <= self.page_size {
let slice = &guard[offset..];
let Some(header) = RecordHeader::decode_opt(slice) else {
break;
};
if header.is_pad() || header.key_len == 0 {
break;
}
let Some(rec_size) = header.checked_record_size() else {
break;
};
if offset + rec_size > self.page_size {
break;
}
let key_start = HEADER_SIZE;
let key_end = key_start + header.key_len as usize;
let key = &slice[key_start..key_end];
let rc_addr = tag_read_cache_addr(page_start_addr + offset as u64);
let prev_addr = header.address();
if prev_addr == 0 {
index.delete(key, rc_addr);
} else {
index.update_address(key, rc_addr, prev_addr);
}
offset += rec_size;
}
}
pub fn with_record<R>(
&self,
tagged_addr: u64,
f: impl FnOnce(&[u8], &[u8], u64) -> R,
) -> Option<R> {
if !self.is_enabled || !is_read_cache_addr(tagged_addr) {
return None;
}
let abs_addr = absolute_address(tagged_addr);
let head = self.head_address.load(Acquire);
let tail = self.tail_address.load(Acquire);
if abs_addr < head || abs_addr >= tail {
return None;
}
let page_id = abs_addr >> self.page_shift;
let offset = (abs_addr & self.page_mask) as usize;
if offset + HEADER_SIZE > self.page_size {
return None;
}
if let Some(page_slice) = unsafe { self.buffer.try_read_page_unlocked(page_id) }
&& abs_addr >= self.head_address.load(Acquire)
&& let Some((key, val, prev_addr)) =
Self::parse_record_at(page_slice, offset, self.page_size - offset)
&& self.buffer.is_page_loaded(page_id)
&& abs_addr >= self.head_address.load(Acquire)
{
return Some(f(key, val, prev_addr));
}
let page_guard = self.buffer.read_page(page_id);
if abs_addr < self.head_address.load(Acquire) || !self.buffer.is_page_loaded(page_id) {
return None;
}
let (key, val, prev_addr) =
Self::parse_record_at(&page_guard, offset, self.page_size - offset)?;
Some(f(key, val, prev_addr))
}
#[inline(always)]
fn parse_record_at(
page_slice: &[u8],
offset: usize,
remaining_in_page: usize,
) -> Option<(&[u8], &[u8], u64)> {
if remaining_in_page < HEADER_SIZE {
return None;
}
let header = RecordHeader::decode_opt(&page_slice[offset..])?;
if header.is_pad() || header.is_tombstone() {
return None;
}
let rec_size = header.checked_record_size()?;
if rec_size > remaining_in_page {
return None;
}
let key_start = offset + HEADER_SIZE;
let key_end = key_start + header.key_len as usize;
let val_end = key_end + header.val_len as usize;
let key = &page_slice[key_start..key_end];
let val = &page_slice[key_end..val_end];
let prev_addr = header.address();
Some((key, val, prev_addr))
}
#[inline]
pub fn skip_read_cache(&self, mut addr: u64) -> u64 {
let mut spins = 0;
while is_read_cache_addr(addr) && spins < 32 {
spins += 1;
match self.with_record(addr, |_k, _v, prev| prev) {
Some(prev) => addr = prev,
None => return 0, }
}
if is_read_cache_addr(addr) {
0 } else {
addr
}
}
}