use std::sync::atomic::Ordering;
use log::debug;
use wdev::Device;
use wram::AlignedBuf;
use wrecord::{HEADER_SIZE, RecordHeader, RecordRef};
use super::{
DISK_READ_CACHE_MASK, DISK_READ_PROBE_LEN, HybridLog, parse_record_from_slice, reject_pad,
};
use crate::{
config::SECTOR_ALIGNMENT,
error::{Error, Result},
flush::PageFlushRange,
output::RecordOutput,
};
#[inline]
fn parse_disk_page_header(
page: &[u8],
offset_in_page: usize,
addr: u64,
page_size: usize,
) -> Result<usize> {
if offset_in_page + HEADER_SIZE > page.len() {
return Err(Error::RecordCorrupted {
addr,
detail: "磁盘读取数据不足记录头大小".into(),
});
}
let header = RecordHeader::from_slice(&page[offset_in_page..offset_in_page + HEADER_SIZE])?;
reject_pad(header, addr)?;
let physical_size = header.physical_size();
if offset_in_page.saturating_add(physical_size) > page_size {
return Err(Error::RecordCorrupted {
addr,
detail: "记录完整内容超出页面容量边界".into(),
});
}
Ok(physical_size)
}
impl<D: Device> HybridLog<D> {
pub fn with_memory_record<R>(
&self,
addr: u64,
f: impl FnOnce(RecordRef<'_>) -> Result<R>,
) -> Result<Option<R>> {
match self.probe_resident(addr)? {
Some(bytes) => {
let rec = parse_record_from_slice(
&bytes,
self.config.page_offset(addr),
addr,
self.config.page_size,
)?;
f(rec).map(Some)
}
None => Ok(None),
}
}
pub async fn read_record(&self, addr: u64) -> Result<RecordOutput> {
if let Some(bytes) = self.probe_resident(addr)? {
let offset = self.config.page_offset(addr);
let rec = parse_record_from_slice(&bytes, offset, addr, self.config.page_size)?;
let physical_size = rec.physical_size();
return Ok(RecordOutput::Memory(
bytes[offset..offset + physical_size].to_vec(),
));
}
let head = self.addresses.head();
if self.addresses.is_on_disk(addr) || (addr >= head && addr < self.addresses.flushed_until()) {
return self.read_disk_record(addr).await;
}
if addr < self.addresses.begin() || addr >= self.addresses.tail() {
Err(Error::AddressOutOfRange {
addr,
begin: self.addresses.begin(),
tail: self.addresses.tail(),
})
} else {
Err(Error::PageNotReady(self.config.page_id(addr)))
}
}
pub async fn read_disk_record(&self, addr: u64) -> Result<RecordOutput> {
let head = self.addresses.head();
if !(self.addresses.is_on_disk(addr) || (addr >= head && addr < self.addresses.flushed_until()))
{
return Err(Error::PageNotReady(self.config.page_id(addr)));
}
let page_size = self.config.page_size;
let page_id = self.config.page_id(addr);
let offset_in_page = self.config.page_offset(addr);
let remaining_in_page = page_size - offset_in_page;
if remaining_in_page < HEADER_SIZE {
return Err(Error::PadRecord(addr));
}
let slot = (page_id as usize) & DISK_READ_CACHE_MASK;
{
let cache = self.disk_read_cache.lock();
if let Some((p, buf)) = cache[slot].as_ref()
&& *p == page_id
{
let page = buf.as_allocated_slice();
let physical_size = parse_disk_page_header(page, offset_in_page, addr, page_size)?;
let record = &page[offset_in_page..offset_in_page + physical_size];
return Ok(RecordOutput::Disk(
self.device.pool().get_from_slice(record)?,
));
}
}
let prev_probe_page = self.last_probe_page.swap(page_id, Ordering::Relaxed);
let page_start = self.config.page_start_address(page_id);
let page_end = page_start + page_size as u64;
if prev_probe_page != page_id
|| page_end > self.addresses.flushed_until()
|| page_end > self.addresses.read_only()
{
let initial_len = DISK_READ_PROBE_LEN.min(remaining_in_page);
let mut buf = self.device.read_range(addr, initial_len).await?;
if buf.len() < HEADER_SIZE {
return Err(Error::RecordCorrupted {
addr,
detail: "磁盘读取数据不足记录头大小".into(),
});
}
let header = RecordHeader::from_slice(&buf[..HEADER_SIZE])?;
reject_pad(header, addr)?;
let physical_size = header.physical_size();
if offset_in_page.saturating_add(physical_size) > page_size {
return Err(Error::RecordCorrupted {
addr,
detail: "记录完整内容超出页面容量边界".into(),
});
}
if buf.len() >= physical_size {
if buf.len() != physical_size {
buf.set_len(physical_size)?;
}
} else {
buf = self.device.read_range(addr, physical_size).await?;
}
return Ok(RecordOutput::Disk(buf));
}
let page = self.device.read_range(page_start, page_size).await?;
let physical_size = parse_disk_page_header(&page, offset_in_page, addr, page_size)?;
let out = RecordOutput::Disk(
self
.device
.pool()
.get_from_slice(&page[offset_in_page..offset_in_page + physical_size])?,
);
if page.len() == page_size {
let mut cache = self.disk_read_cache.lock();
if cache[slot].as_ref().is_none_or(|(p, _)| *p != page_id) {
let mut buf = match cache[slot].take() {
Some((_, buf)) => buf,
None => AlignedBuf::new(page_size, SECTOR_ALIGNMENT).map_err(Error::from)?,
};
buf.as_allocated_slice_mut()[..page_size].copy_from_slice(&page);
cache[slot] = Some((page_id, buf));
}
}
Ok(out)
}
pub async fn flush_page(&self, page_id: u64) -> Result<()> {
self.flush_pages_range(page_id, page_id).await
}
pub async fn flush_pages_range(&self, start_page: u64, end_page: u64) -> Result<()> {
if start_page > end_page {
return Ok(());
}
let from_addr = self.config.page_start_address(start_page);
let until_addr = self.config.page_start_address(end_page.saturating_add(1));
let merged_range = self
.pending_flush
.coalesce(PageFlushRange::new(from_addr, until_addr));
let merged_range = self.clamp_flush_range(merged_range);
if merged_range.is_empty() {
return Ok(());
}
let actual_start_page = self.config.page_id(merged_range.from_address);
let actual_end_page = self
.config
.page_id(merged_range.until_address.saturating_sub(1));
let total_pages = actual_end_page
.saturating_sub(actual_start_page)
.saturating_add(1) as usize;
let total_bytes = merged_range.len() as usize;
let mut staging = match self.take_flush_staging(total_bytes) {
Some(buf) => buf,
None => AlignedBuf::new(total_bytes, SECTOR_ALIGNMENT).map_err(Error::from)?,
};
debug_assert!(staging.capacity() >= total_bytes);
unsafe { staging.set_len_unchecked(total_bytes) };
let page_size = self.config.page_size;
let mut err = None;
'copy: for (i, p) in (actual_start_page..=actual_end_page).enumerate() {
if !self.buffer.is_page_loaded(p) {
err = Some(Error::PageNotReady(p));
break 'copy;
}
let guard = self.buffer.read_page(p);
let dest_offset = i * page_size;
staging[dest_offset..dest_offset + page_size].copy_from_slice(&guard);
}
if let Some(e) = err {
self.restore_flush_staging(staging);
self.requeue_flush_range(merged_range);
return Err(e);
}
let (res, buf) = self
.device
.write_aligned(merged_range.from_address, staging)
.await;
staging = buf;
let flush_res = match res {
Ok(n) if n == total_bytes => Ok(()),
Ok(n) => Err(Error::FlushFailed {
page_id: actual_start_page,
detail: format!("设备短写: 已写 {n}/{total_bytes} 字节"),
}),
Err(e) => Err(e.into()),
};
if let Err(e) = flush_res {
self.restore_flush_staging(staging);
self.requeue_flush_range(merged_range);
return Err(e);
}
let done_until = merged_range.until_address.min(self.addresses.tail());
self.pending_flush.complete_flush_range(
PageFlushRange::new(merged_range.from_address, done_until),
&self.addresses,
);
self.restore_flush_staging(staging);
debug!(
"页面范围 [{actual_start_page}..={actual_end_page}] ({total_pages} 页) 成功聚合落盘至偏移 {:#x} ~ {:#x}",
merged_range.from_address, merged_range.until_address
);
Ok(())
}
fn take_flush_staging(&self, required: usize) -> Option<AlignedBuf> {
let mut cache = self.flush_staging.lock();
match cache.take() {
Some(buf) if buf.capacity() >= required => Some(buf),
_ => None,
}
}
fn clamp_flush_range(&self, range: PageFlushRange) -> PageFlushRange {
let floor = self
.config
.page_start_address(self.config.page_id(self.addresses.flushed_until()));
if range.from_address < floor {
PageFlushRange::new(floor, range.until_address)
} else {
range
}
}
fn requeue_flush_range(&self, range: PageFlushRange) {
let clamped = self.clamp_flush_range(range);
if !clamped.is_empty() {
self.pending_flush.add(clamped);
}
}
fn restore_flush_staging(&self, staging: AlignedBuf) {
let mut cache = self.flush_staging.lock();
if cache
.as_ref()
.is_none_or(|c| staging.capacity() > c.capacity())
{
*cache = Some(staging);
}
}
pub async fn sync(&self) -> Result<()> {
self.device.sync().await.map_err(Error::from)
}
pub async fn flush_all(&self) -> Result<u64> {
let tail = self.addresses.tail();
let flushed = self.addresses.flushed_until();
if flushed < tail {
let start_page = self.config.page_id(flushed);
let end_page = self.config.page_id(tail.saturating_sub(1));
self.flush_pages_range(start_page, end_page).await?;
}
Ok(self.addresses.flushed_until())
}
pub async fn iterate_version_chain<F>(&self, start_addr: u64, mut f: F) -> Result<()>
where
F: FnMut(u64, &RecordOutput) -> Result<bool>,
{
let mut curr = start_addr;
let begin = self.addresses.begin();
while curr >= begin && curr != 0 {
let record = self.read_record(curr).await?;
let prev = record.prev_address()?;
let cont = f(curr, &record)?;
if !cont || prev == 0 || prev >= curr {
break;
}
curr = prev;
}
Ok(())
}
}