use std::pin::Pin;
use std::io::Result;
use std::collections::BTreeMap;
use crate::SegmentId;
pub mod config;
pub(crate) mod s3;
#[derive(Debug)]
pub struct WalChunkDesc {
pub seq: usize,
pub segid: SegmentId,
pub key: String,
pub offset: usize,
pub len: usize,
pub is_zero: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WalBarrier {
pub entries: Vec<(usize, usize, usize)>,
}
const WAL_BARRIER_MAGIC: u32 = 0x5741_4C42; const WAL_BARRIER_VERSION: u32 = 1;
impl WalBarrier {
pub fn new(entries: Vec<(usize, usize, usize)>) -> Self {
Self { entries }
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(16 + self.entries.len() * 24);
buf.extend_from_slice(&WAL_BARRIER_MAGIC.to_ne_bytes());
buf.extend_from_slice(&WAL_BARRIER_VERSION.to_ne_bytes());
buf.extend_from_slice(&(self.entries.len() as u64).to_ne_bytes());
for (seq, off, len) in self.entries.iter() {
buf.extend_from_slice(&(*seq as u64).to_ne_bytes());
buf.extend_from_slice(&(*off as u64).to_ne_bytes());
buf.extend_from_slice(&(*len as u64).to_ne_bytes());
}
buf
}
pub fn decode(buf: &[u8]) -> Option<Self> {
if buf.len() < 16 {
return None;
}
let magic = u32::from_ne_bytes(buf[0..4].try_into().ok()?);
let version = u32::from_ne_bytes(buf[4..8].try_into().ok()?);
if magic != WAL_BARRIER_MAGIC || version != WAL_BARRIER_VERSION {
return None;
}
let count = u64::from_ne_bytes(buf[8..16].try_into().ok()?) as usize;
if buf.len() != 16 + count * 24 {
return None;
}
let mut entries = Vec::with_capacity(count);
for i in 0..count {
let at = 16 + i * 24;
let seq = u64::from_ne_bytes(buf[at..at + 8].try_into().ok()?) as usize;
let off = u64::from_ne_bytes(buf[at + 8..at + 16].try_into().ok()?) as usize;
let len = u64::from_ne_bytes(buf[at + 16..at + 24].try_into().ok()?) as usize;
entries.push((seq, off, len));
}
Some(Self { entries })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct WalRecoveryReport {
pub replayed: bool,
pub landed_on_barrier: bool,
pub records_dropped: usize,
}
impl Default for WalRecoveryReport {
fn default() -> Self {
Self {
replayed: false,
landed_on_barrier: true,
records_dropped: 0,
}
}
}
pub trait WalReadWrite {
fn write(&mut self, segid: SegmentId, offset: usize, buf: &[u8]) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
fn write_zero(&mut self, segid: SegmentId, offset: usize, len: usize) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
fn read(&self, seq: usize, segid: SegmentId, offset: usize, len: usize) -> Pin<Box<dyn Future<Output = Result<Vec<u8>>> + Send + '_>>;
fn write_barrier(&mut self, segid: SegmentId) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
fn discard_pending(&mut self);
fn read_barrier(&self, segid: SegmentId) -> Pin<Box<dyn Future<Output = Result<Option<WalBarrier>>> + Send + '_>>;
fn next_seq_peek(&self, segid: SegmentId) -> usize;
fn write_txn_marker(&mut self, segid: SegmentId, from_seq: usize) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
fn read_txn_marker(&self, segid: SegmentId) -> Pin<Box<dyn Future<Output = Result<Option<usize>>> + Send + '_>>;
fn delete_txn_marker(&mut self, segid: SegmentId) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
fn list_segments(&self) -> Pin<Box<dyn Future<Output = Result<Vec<SegmentId>>> + Send + '_>>;
fn list_chunks(&self, segid: SegmentId) -> Pin<Box<dyn Future<Output = Result<BTreeMap<usize, WalChunkDesc>>> + Send + '_>>;
fn delete_segment(&self, segid: SegmentId) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn barrier_round_trips() {
for entries in [
vec![],
vec![(0usize, 0usize, 4096usize)],
vec![(0, 0, 4096), (1, 4096, 8192), (2, 65536, 200)],
] {
let b = WalBarrier::new(entries.clone());
let decoded = WalBarrier::decode(&b.encode()).expect("must round trip");
assert_eq!(decoded.entries, entries);
}
}
#[test]
fn barrier_rejects_what_it_cannot_vouch_for() {
assert!(WalBarrier::decode(&[]).is_none(), "empty");
assert!(WalBarrier::decode(&[0u8; 8]).is_none(), "shorter than the header");
let good = WalBarrier::new(vec![(1, 4096, 4096)]).encode();
assert!(WalBarrier::decode(&good).is_some());
let mut wrong_magic = good.clone();
wrong_magic[0] ^= 0xFF;
assert!(WalBarrier::decode(&wrong_magic).is_none(), "magic");
let mut wrong_version = good.clone();
wrong_version[4] = 0xFE;
assert!(WalBarrier::decode(&wrong_version).is_none(), "version");
let mut truncated = good.clone();
truncated.truncate(16);
assert!(WalBarrier::decode(&truncated).is_none(), "truncated body");
let mut trailing = good.clone();
trailing.push(0);
assert!(WalBarrier::decode(&trailing).is_none(), "trailing bytes");
}
}