use crate::source::{FromOffset, ReplicationSource};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FeedRead {
Resync {
generation: u64,
tail: u64,
},
Future,
}
pub struct FeedFrame<'a> {
pub offset: u64,
pub bytes: &'a [u8],
}
pub struct FeedSource {
generation: u64,
source: ReplicationSource,
}
impl FeedSource {
pub fn new(generation: u64, source: ReplicationSource) -> Self {
Self { generation, source }
}
pub fn generation(&self) -> u64 {
self.generation
}
pub fn source(&self) -> &ReplicationSource {
&self.source
}
pub fn source_mut(&mut self) -> &mut ReplicationSource {
&mut self.source
}
pub fn bump_generation(&mut self) {
self.generation += 1;
let budget = self.source.max_bytes();
self.source = ReplicationSource::new(budget);
}
pub fn tail(&self) -> (u64, u64) {
(self.generation, self.source.next_offset())
}
pub fn read(
&self,
cursor_gen: u64,
offset: u64,
max: usize,
) -> Result<Vec<FeedFrame<'_>>, FeedRead> {
if cursor_gen > self.generation {
return Err(FeedRead::Future);
}
if cursor_gen < self.generation {
let (generation, tail) = self.tail();
return Err(FeedRead::Resync { generation, tail });
}
match self.source.frames_from(offset) {
Ok(iter) => Ok(iter
.take(max)
.map(|f| FeedFrame { offset: f.offset, bytes: &f.bytes })
.collect()),
Err(FromOffset::TooOld) => {
let (generation, tail) = self.tail();
Err(FeedRead::Resync { generation, tail })
}
Err(FromOffset::Future) => Err(FeedRead::Future),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use kevy_resp::Argv;
fn argv(parts: &[&[u8]]) -> Argv {
let mut a = Argv::default();
for p in parts {
a.push(p);
}
a
}
fn feed_with(n: usize) -> FeedSource {
let mut f = FeedSource::new(1, ReplicationSource::new(1 << 20));
for i in 0..n {
f.source_mut().push_mutation(&argv(&[b"SET", format!("k{i}").as_bytes(), b"v"]));
}
f
}
#[test]
fn read_serves_in_order_and_caps_at_max() {
let f = feed_with(5);
let frames = f.read(1, 0, 3).unwrap();
assert_eq!(frames.len(), 3);
assert_eq!(frames[0].offset, 0);
assert_eq!(frames[2].offset, 2);
assert!(f.read(1, 5, 10).unwrap().is_empty());
}
#[test]
fn stale_generation_resyncs_with_tail() {
let mut f = feed_with(3);
f.bump_generation();
f.source_mut().push_mutation(&argv(&[b"SET", b"new", b"v"]));
match f.read(1, 2, 10) {
Err(FeedRead::Resync { generation, tail }) => {
assert_eq!(generation, 2);
assert_eq!(tail, 1);
}
other => panic!("expected Resync, got {:?}", other.map(|v| v.len())),
}
let frames = f.read(2, 0, 10).unwrap();
assert_eq!(frames.len(), 1);
}
#[test]
fn future_cursors_rejected() {
let f = feed_with(2);
assert!(matches!(f.read(3, 0, 1), Err(FeedRead::Future)));
assert!(matches!(f.read(1, 99, 1), Err(FeedRead::Future)));
}
#[test]
fn evicted_offset_resyncs() {
let mut f = FeedSource::new(1, ReplicationSource::new(64));
for i in 0..50 {
f.source_mut().push_mutation(&argv(&[b"SET", format!("k{i}").as_bytes(), b"v"]));
}
match f.read(1, 0, 10) {
Err(FeedRead::Resync { generation, tail }) => {
assert_eq!(generation, 1);
assert_eq!(tail, 50);
}
other => panic!("expected Resync, got {:?}", other.map(|v| v.len())),
}
}
}