use super::*;
use crate::background::{CheckpointBytes, CheckpointWorkContext, CheckpointWorkError};
mod reader;
pub(crate) use reader::{CheckpointBinaryWalReader, CheckpointWalReadEvent};
#[cfg(test)]
mod stream_tests;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod writer_tests;
pub(crate) struct CheckpointWalFrameStream<'a> {
generation: u64,
remaining: &'a [u8],
first_capacity: usize,
padding: usize,
first: bool,
finished: bool,
encoded_len: usize,
work: &'a CheckpointWorkContext,
}
pub(crate) struct CheckpointWalFragment<'a> {
header: [u8; WAL_FRAGMENT_HEADER_BYTES],
body: &'a [u8],
padding: usize,
}
impl CheckpointWalFragment<'_> {
pub(crate) fn parts(&self) -> [&[u8]; 3] {
[
&[0; WAL_FRAGMENT_HEADER_BYTES][..self.padding],
&self.header,
self.body,
]
}
pub(crate) fn encoded_len(&self) -> usize {
self.padding + self.header.len() + self.body.len()
}
pub(crate) fn write_to<W: std::io::Write>(&self, output: &mut W) -> std::io::Result<()> {
let mut slices = self.parts().map(std::io::IoSlice::new);
let mut remaining = &mut slices[..];
std::io::IoSlice::advance_slices(&mut remaining, 0);
while !remaining.is_empty() {
match output.write_vectored(remaining) {
Ok(0) => {
return Err(std::io::Error::new(
std::io::ErrorKind::WriteZero,
"checkpoint WAL fragment write made no progress",
));
}
Ok(written) => std::io::IoSlice::advance_slices(&mut remaining, written),
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
Err(error) => return Err(error),
}
}
Ok(())
}
}
impl<'a> CheckpointWalFrameStream<'a> {
pub(crate) fn new(
generation: u64,
payload: &'a [u8],
position: u64,
work: &'a CheckpointWorkContext,
) -> std::result::Result<Self, CheckpointWorkError> {
let remaining_block = WAL_BLOCK_BYTES - (position % WAL_BLOCK_BYTES as u64) as usize;
let padding = if remaining_block < WAL_FRAGMENT_HEADER_BYTES {
remaining_block
} else {
0
};
let first_capacity = if padding != 0 {
WAL_BLOCK_BYTES - WAL_FRAGMENT_HEADER_BYTES
} else {
remaining_block - WAL_FRAGMENT_HEADER_BYTES
};
let remainder = payload.len().saturating_sub(first_capacity);
let more_fragments = if remainder == 0 {
0
} else {
(remainder - 1) / (WAL_BLOCK_BYTES - WAL_FRAGMENT_HEADER_BYTES) + 1
};
let encoded_len = more_fragments
.checked_add(1)
.and_then(|fragments| fragments.checked_mul(WAL_FRAGMENT_HEADER_BYTES))
.and_then(|headers| headers.checked_add(payload.len()))
.and_then(|bytes| bytes.checked_add(padding))
.ok_or_else(|| {
work.record_failure(CheckpointWorkError::Allocation {
bytes: u64::MAX,
reason: "framed WAL byte capacity overflows usize".into(),
})
})?;
Ok(Self {
generation,
remaining: payload,
first_capacity,
padding,
first: true,
finished: false,
encoded_len,
work,
})
}
pub(crate) fn encoded_len(&self) -> usize {
self.encoded_len
}
pub(crate) fn next(
&mut self,
) -> std::result::Result<Option<CheckpointWalFragment<'a>>, CheckpointWorkError> {
self.work.checkpoint()?;
if self.finished {
return Ok(None);
}
let capacity = if self.first {
self.first_capacity
} else {
WAL_BLOCK_BYTES - WAL_FRAGMENT_HEADER_BYTES
};
let (chunk, rest) = self.remaining.split_at(capacity.min(self.remaining.len()));
let fragment_type = match (self.first, rest.is_empty()) {
(true, true) => FRAGMENT_FULL,
(true, false) => FRAGMENT_FIRST,
(false, true) => FRAGMENT_LAST,
(false, false) => FRAGMENT_MIDDLE,
};
let unit = self.work.start_unit()?;
let crc = masked_fragment_crc(fragment_type, self.generation, chunk);
let mut header = [0; WAL_FRAGMENT_HEADER_BYTES];
header[..4].copy_from_slice(&crc.to_le_bytes());
header[4..6].copy_from_slice(&(chunk.len() as u16).to_le_bytes());
header[6] = fragment_type;
header[7..].copy_from_slice(&self.generation.to_le_bytes());
unit.finish();
self.work.checkpoint()?;
let fragment = CheckpointWalFragment {
header,
body: chunk,
padding: if self.first { self.padding } else { 0 },
};
self.first = false;
self.finished = rest.is_empty();
self.remaining = rest;
Ok(Some(fragment))
}
}
#[cfg(test)]
pub(crate) fn frame_binary_wal_record_with_work_context(
generation: u64,
payload: &[u8],
position: u64,
work: &CheckpointWorkContext,
) -> std::result::Result<CheckpointBytes, CheckpointWorkError> {
let block_pos = (position % WAL_BLOCK_BYTES as u64) as usize;
let remaining_block = WAL_BLOCK_BYTES - block_pos;
let padding = if remaining_block < WAL_FRAGMENT_HEADER_BYTES {
remaining_block
} else {
0
};
let first_capacity = if padding != 0 {
WAL_BLOCK_BYTES - WAL_FRAGMENT_HEADER_BYTES
} else {
remaining_block - WAL_FRAGMENT_HEADER_BYTES
};
let remainder = payload.len().saturating_sub(first_capacity);
let more_fragments = if remainder == 0 {
0
} else {
(remainder - 1) / (WAL_BLOCK_BYTES - WAL_FRAGMENT_HEADER_BYTES) + 1
};
let capacity = more_fragments
.checked_add(1)
.and_then(|fragments| fragments.checked_mul(WAL_FRAGMENT_HEADER_BYTES))
.and_then(|headers| headers.checked_add(payload.len()))
.and_then(|bytes| bytes.checked_add(padding))
.ok_or_else(|| {
work.record_failure(CheckpointWorkError::Allocation {
bytes: u64::MAX,
reason: "framed WAL byte capacity overflows usize".into(),
})
})?;
let mut output = CheckpointBytes::new(capacity, work)?;
if padding != 0 {
output.append(&[0; WAL_FRAGMENT_HEADER_BYTES][..padding], work)?;
}
let mut available = first_capacity;
let mut remaining = payload;
let mut first = true;
loop {
let take = available.min(remaining.len());
let (chunk, rest) = remaining.split_at(take);
let fragment_type = match (first, rest.is_empty()) {
(true, true) => FRAGMENT_FULL,
(true, false) => FRAGMENT_FIRST,
(false, true) => FRAGMENT_LAST,
(false, false) => FRAGMENT_MIDDLE,
};
let unit = work.start_unit()?;
let crc = masked_fragment_crc(fragment_type, generation, chunk);
let mut header = [0; WAL_FRAGMENT_HEADER_BYTES];
header[..4].copy_from_slice(&crc.to_le_bytes());
header[4..6].copy_from_slice(&(take as u16).to_le_bytes());
header[6] = fragment_type;
header[7..].copy_from_slice(&generation.to_le_bytes());
unit.finish();
output.append(&header, work)?;
output.append(chunk, work)?;
if rest.is_empty() {
work.checkpoint()?;
debug_assert_eq!(output.len(), capacity);
return Ok(output);
}
remaining = rest;
first = false;
available = WAL_BLOCK_BYTES - WAL_FRAGMENT_HEADER_BYTES;
}
}