use std::sync::atomic::Ordering;
use wdev::Device;
use wram::AlignedBuf;
use wrecord::{HEADER_SIZE, RecordHeader, RecordRef};
use crate::{
address::AddressManager,
error::{Error, Result},
hlog::{HybridLog, PAD_KEY_LEN, PageBytes, parse_record_from_slice},
output::RecordOutput,
};
pub struct ScanItem<'a> {
pub addr: u64,
pub rec: RecordRef<'a>,
pub bytes: &'a [u8],
}
pub struct ScanIterator<'a, D: Device> {
hlog: &'a HybridLog<D>,
curr_addr: u64,
end_addr: u64,
read_only: u64,
disk_page_cache: Option<(u64, AlignedBuf)>,
}
impl<'a, D: Device> ScanIterator<'a, D> {
pub fn new(hlog: &'a HybridLog<D>, begin_addr: u64, end_addr: u64) -> Self {
let begin = hlog.addresses.begin_address.load(Ordering::Acquire);
let read_only = hlog.addresses.read_only_address.load(Ordering::Acquire);
Self {
hlog,
curr_addr: begin_addr.max(begin),
end_addr,
read_only,
disk_page_cache: None,
}
}
#[inline]
pub const fn current_address(&self) -> u64 {
self.curr_addr
}
pub async fn next_ref<R>(
&mut self,
f: impl FnOnce(ScanItem<'_>) -> Result<R>,
) -> Result<Option<R>> {
let tail = self.hlog.addresses.tail_address.load(Ordering::Acquire);
let effective_end = self.end_addr.min(tail);
while self.curr_addr < effective_end {
let curr_addr = self.curr_addr;
let page_id = self.hlog.config.page_id(curr_addr);
let offset = self.hlog.config.page_offset(curr_addr);
let page_size = self.hlog.config.page_size;
if offset + HEADER_SIZE > page_size {
self.skip_to_next_page(page_id);
continue;
}
let head = self.hlog.addresses.head_address.load(Ordering::Acquire);
let bytes = if curr_addr < head {
match self.disk_page_cache.take() {
Some((cached_page, buf)) if cached_page == page_id => PageBytes::Disk(buf),
_ => PageBytes::Disk(
self
.hlog
.device
.read_range(self.hlog.config.page_start_address(page_id), page_size)
.await?,
),
}
} else {
self.disk_page_cache = None;
let flushed_until = self.hlog.addresses.flushed_until();
if !AddressManager::is_mutable_snapshot(curr_addr, head, self.read_only, tail)
&& let Some(page_slice) = unsafe { self.hlog.buffer.try_read_page_unlocked(page_id) }
&& curr_addr >= self.hlog.addresses.head()
{
PageBytes::Raw(page_slice)
} else {
let guard = self.hlog.buffer.read_page(page_id);
if curr_addr >= self.hlog.addresses.head() && self.hlog.buffer.is_page_loaded(page_id) {
PageBytes::Locked(guard)
} else {
drop(guard);
if curr_addr < flushed_until {
PageBytes::Disk(
self
.hlog
.device
.read_range(self.hlog.config.page_start_address(page_id), page_size)
.await?,
)
} else {
self.skip_to_next_page(page_id);
continue;
}
}
}
};
match parse_record_from_slice(&bytes, offset, curr_addr, page_size) {
Ok(rec) => {
let physical_size = rec.physical_size();
let out = f(ScanItem {
addr: curr_addr,
rec,
bytes: &bytes[offset..offset + physical_size],
})?;
if let PageBytes::Disk(buf) = bytes {
self.disk_page_cache = Some((page_id, buf));
}
self.advance(curr_addr, physical_size, page_id);
return Ok(Some(out));
}
Err(Error::PadRecord(_)) => {
let pad_step = bytes
.get(offset..offset + HEADER_SIZE)
.and_then(|h| RecordHeader::from_slice(h).ok())
.filter(|h| h.key_len == PAD_KEY_LEN)
.map(|h| (HEADER_SIZE + h.val_len as usize).min(page_size - offset));
match pad_step {
Some(step) if offset + step < page_size => {
if let PageBytes::Disk(buf) = bytes {
self.disk_page_cache = Some((page_id, buf));
}
self.curr_addr = curr_addr + step as u64;
}
_ => self.skip_to_next_page(page_id),
}
}
Err(e) => return Err(e),
}
}
Ok(None)
}
pub async fn next(&mut self) -> Result<Option<(u64, RecordOutput)>> {
self
.next_ref(|item| Ok((item.addr, RecordOutput::Memory(item.bytes.to_vec()))))
.await
}
pub async fn next_into<'b>(
&'b mut self,
buf: &'b mut Vec<u8>,
) -> Result<Option<(u64, &'b [u8])>> {
let addr = self
.next_ref(|item| {
buf.clear();
buf.extend_from_slice(item.bytes);
Ok(item.addr)
})
.await?;
Ok(addr.map(|a| (a, buf.as_slice())))
}
#[inline]
fn skip_to_next_page(&mut self, page_id: u64) {
self.curr_addr = self.hlog.config.page_start_address(page_id + 1);
}
#[inline]
fn advance(&mut self, curr_addr: u64, total_size: usize, page_id: u64) {
if self.hlog.config.page_offset(curr_addr) + total_size == self.hlog.config.page_size {
self.skip_to_next_page(page_id);
} else {
self.curr_addr = curr_addr + total_size as u64;
}
}
}
impl<D: Device> HybridLog<D> {
pub fn scan_iter(&self, begin_addr: u64, end_addr: u64) -> ScanIterator<'_, D> {
ScanIterator::new(self, begin_addr, end_addr)
}
pub async fn scan<F>(&self, begin_addr: u64, end_addr: u64, mut f: F) -> Result<()>
where
F: FnMut(u64, RecordRef<'_>) -> Result<bool>,
{
let mut it = self.scan_iter(begin_addr, end_addr);
while let Some(cont) = it.next_ref(|item| f(item.addr, item.rec)).await? {
if !cont {
break;
}
}
Ok(())
}
}