1use crate::wire::encode_frame;
23use kevy_resp::ArgvView;
24#[cfg(test)]
25use kevy_resp::Argv;
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct Frame {
30 pub offset: u64,
32 pub bytes: Vec<u8>,
34}
35
36#[derive(Debug, PartialEq, Eq)]
39pub enum FromOffset {
40 TooOld,
43 Future,
47}
48
49pub struct ReplicationSource {
51 next_offset: u64,
52 bytes_in_buf: usize,
53 max_bytes: usize,
54 buf: std::collections::VecDeque<Frame>,
55}
56
57impl ReplicationSource {
58 pub fn new(max_bytes: usize) -> Self {
63 assert!(max_bytes > 0, "ReplicationSource max_bytes must be > 0");
64 Self {
65 next_offset: 0,
66 bytes_in_buf: 0,
67 max_bytes,
68 buf: std::collections::VecDeque::new(),
69 }
70 }
71
72 pub fn set_next_offset(&mut self, next: u64) {
76 assert!(self.buf.is_empty(), "set_next_offset on non-empty backlog");
77 self.next_offset = next;
78 }
79
80 pub fn max_bytes(&self) -> usize {
82 self.max_bytes
83 }
84
85 pub fn next_offset(&self) -> u64 {
88 self.next_offset
89 }
90
91 pub fn oldest_offset(&self) -> Option<u64> {
93 self.buf.front().map(|f| f.offset)
94 }
95
96 pub fn newest_offset(&self) -> Option<u64> {
98 self.buf.back().map(|f| f.offset)
99 }
100
101 pub fn buffered_bytes(&self) -> usize {
103 self.bytes_in_buf
104 }
105
106 pub fn len(&self) -> usize {
108 self.buf.len()
109 }
110
111 pub fn is_empty(&self) -> bool {
113 self.buf.is_empty()
114 }
115
116 pub fn push_mutation<A: ArgvView + ?Sized>(&mut self, argv: &A) -> u64 {
126 let offset = self.next_offset;
127 let bytes = encode_frame(offset, argv);
128 let frame_len = bytes.len();
129
130 while self.bytes_in_buf + frame_len > self.max_bytes && !self.buf.is_empty() {
133 let dropped = self.buf.pop_front().expect("non-empty checked");
134 self.bytes_in_buf -= dropped.bytes.len();
135 }
136
137 self.bytes_in_buf += frame_len;
138 self.buf.push_back(Frame { offset, bytes });
139 self.next_offset = self
140 .next_offset
141 .checked_add(1)
142 .expect("replication offset wrap — i64::MAX guard tripped");
143 offset
144 }
145
146 pub fn drop_up_to(&mut self, watermark: u64) {
156 while let Some(front) = self.buf.front() {
157 if front.offset >= watermark {
158 break;
159 }
160 let dropped = self.buf.pop_front().expect("front-of-loop");
161 self.bytes_in_buf -= dropped.bytes.len();
162 }
163 }
164
165 pub fn frames_from(&self, from: u64) -> Result<FramesIter<'_>, FromOffset> {
175 if from > self.next_offset {
176 return Err(FromOffset::Future);
177 }
178 if from == self.next_offset {
180 return Ok(FramesIter {
181 buf: &self.buf,
182 cursor: self.buf.len(),
183 });
184 }
185 match self.oldest_offset() {
192 Some(oldest) if from < oldest => return Err(FromOffset::TooOld),
193 None => return Err(FromOffset::TooOld),
194 _ => {}
195 }
196 let start = self.buf.iter().position(|f| f.offset >= from);
199 Ok(FramesIter {
200 buf: &self.buf,
201 cursor: start.unwrap_or(self.buf.len()),
202 })
203 }
204}
205
206pub struct FramesIter<'a> {
208 buf: &'a std::collections::VecDeque<Frame>,
209 cursor: usize,
210}
211
212impl<'a> Iterator for FramesIter<'a> {
213 type Item = &'a Frame;
214 fn next(&mut self) -> Option<&'a Frame> {
215 let item = self.buf.get(self.cursor)?;
216 self.cursor += 1;
217 Some(item)
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224 use crate::wire::decode_frame;
225
226 fn argv(args: &[&[u8]]) -> Argv {
227 let mut a = Argv::default();
228 for arg in args {
229 a.push(arg);
230 }
231 a
232 }
233
234 #[test]
235 fn fresh_source_is_empty() {
236 let s = ReplicationSource::new(1024);
237 assert!(s.is_empty());
238 assert_eq!(s.len(), 0);
239 assert_eq!(s.next_offset(), 0);
240 assert_eq!(s.oldest_offset(), None);
241 assert_eq!(s.newest_offset(), None);
242 assert_eq!(s.buffered_bytes(), 0);
243 }
244
245 #[test]
246 fn push_assigns_monotonic_offsets() {
247 let mut s = ReplicationSource::new(64 * 1024);
248 let o0 = s.push_mutation(&argv(&[b"SET", b"a", b"1"]));
249 let o1 = s.push_mutation(&argv(&[b"SET", b"b", b"2"]));
250 let o2 = s.push_mutation(&argv(&[b"DEL", b"a"]));
251 assert_eq!((o0, o1, o2), (0, 1, 2));
252 assert_eq!(s.oldest_offset(), Some(0));
253 assert_eq!(s.newest_offset(), Some(2));
254 assert_eq!(s.next_offset(), 3);
255 assert_eq!(s.len(), 3);
256 }
257
258 #[test]
259 fn pushed_frames_decode_back_to_the_pushed_argv() {
260 let mut s = ReplicationSource::new(1024);
261 let a = argv(&[b"HSET", b"h", b"f", b"v"]);
262 let off = s.push_mutation(&a);
263 let frame = s.buf.front().expect("one frame");
264 assert_eq!(frame.offset, off);
265 let (decoded_off, decoded_argv, used) = decode_frame(&frame.bytes).expect("decode");
266 assert_eq!(decoded_off, off);
267 assert_eq!(decoded_argv, a);
268 assert_eq!(used, frame.bytes.len());
269 }
270
271 #[test]
272 fn eviction_drops_oldest_when_budget_exceeded() {
273 let mut s = ReplicationSource::new(80);
276 let _ = s.push_mutation(&argv(&[b"SET", b"a", b"1"]));
277 let _ = s.push_mutation(&argv(&[b"SET", b"b", b"2"]));
278 assert_eq!(s.oldest_offset(), Some(0));
279 let _ = s.push_mutation(&argv(&[b"SET", b"c", b"3"]));
280 assert_eq!(s.oldest_offset(), Some(1));
281 assert_eq!(s.newest_offset(), Some(2));
282 assert!(s.buffered_bytes() <= 80);
283 assert_eq!(s.next_offset(), 3);
285 }
286
287 #[test]
288 fn oversized_single_frame_is_retained_against_budget() {
289 let mut s = ReplicationSource::new(8);
293 let off = s.push_mutation(&argv(&[b"SET", b"k", b"v"]));
294 assert_eq!(s.len(), 1);
295 assert_eq!(s.oldest_offset(), Some(off));
296 assert!(s.buffered_bytes() > 8); let off2 = s.push_mutation(&argv(&[b"DEL", b"k"]));
299 assert_eq!(s.len(), 1);
300 assert_eq!(s.oldest_offset(), Some(off2));
301 }
302
303 #[test]
304 fn frames_from_at_exact_offset_returns_that_frame_first() {
305 let mut s = ReplicationSource::new(1024);
306 for i in 0..5 {
307 let _ = s.push_mutation(&argv(&[b"SET", b"k", format!("{i}").as_bytes()]));
308 }
309 let mut it = s.frames_from(2).unwrap();
310 let f = it.next().expect("frame");
311 assert_eq!(f.offset, 2);
312 let remaining: Vec<u64> = it.map(|f| f.offset).collect();
313 assert_eq!(remaining, vec![3, 4]);
314 }
315
316 #[test]
317 fn frames_from_at_next_offset_is_empty_caught_up() {
318 let mut s = ReplicationSource::new(1024);
319 let _ = s.push_mutation(&argv(&[b"PING"]));
320 let _ = s.push_mutation(&argv(&[b"PING"]));
321 let it = s.frames_from(s.next_offset()).unwrap();
322 assert_eq!(it.count(), 0);
323 }
324
325 #[test]
326 fn frames_from_too_old_after_eviction() {
327 let mut s = ReplicationSource::new(80);
329 for _ in 0..5 {
330 let _ = s.push_mutation(&argv(&[b"SET", b"k", b"v"]));
331 }
332 assert!(s.oldest_offset().unwrap() > 0);
334 assert!(matches!(s.frames_from(0), Err(FromOffset::TooOld)));
335 }
336
337 #[test]
338 fn frames_from_future_offset_rejected() {
339 let mut s = ReplicationSource::new(1024);
340 let _ = s.push_mutation(&argv(&[b"PING"]));
341 assert!(matches!(s.frames_from(2), Err(FromOffset::Future)));
343 }
344
345 #[test]
346 fn frames_from_empty_source_at_zero_is_caught_up_not_too_old() {
347 let s = ReplicationSource::new(1024);
351 assert_eq!(s.frames_from(0).unwrap().count(), 0);
352 assert!(matches!(s.frames_from(1), Err(FromOffset::Future)));
354 }
355
356 #[test]
357 fn push_mutation_accepts_argv_borrowed_from_dispatcher_hot_path() {
358 let resp = b"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
365 let (borrowed, consumed) = kevy_resp::parse_command_borrowed(resp)
366 .expect("parse ok")
367 .expect("complete frame");
368 assert_eq!(consumed, resp.len());
369
370 let mut s = ReplicationSource::new(1024);
371 let off = s.push_mutation(&borrowed);
372 assert_eq!(off, 0);
373
374 let frame = s.buf.front().expect("one frame");
375 let (decoded_off, decoded_argv, _) =
376 crate::wire::decode_frame(&frame.bytes).expect("decode");
377 assert_eq!(decoded_off, 0);
378 assert_eq!(decoded_argv, argv(&[b"SET", b"foo", b"bar"]));
379 }
380
381 #[test]
382 fn buffered_bytes_tracks_actual_frame_total() {
383 let mut s = ReplicationSource::new(1024);
384 let _ = s.push_mutation(&argv(&[b"SET", b"k", b"v"]));
385 let _ = s.push_mutation(&argv(&[b"DEL", b"k"]));
386 let actual: usize = s.buf.iter().map(|f| f.bytes.len()).sum();
387 assert_eq!(s.buffered_bytes(), actual);
388 }
389
390 #[test]
391 fn drop_up_to_evicts_below_watermark() {
392 let mut s = ReplicationSource::new(64 * 1024);
394 for i in 0..5 {
395 let v = format!("v{i}");
396 let _ = s.push_mutation(&argv(&[b"SET", b"k", v.as_bytes()]));
397 }
398 assert_eq!(s.len(), 5);
399 let bytes_before = s.buffered_bytes();
400 s.drop_up_to(3);
402 assert_eq!(s.len(), 2);
403 assert_eq!(s.oldest_offset(), Some(3));
404 assert_eq!(s.newest_offset(), Some(4));
405 assert!(s.buffered_bytes() < bytes_before);
407 let kept: Vec<_> = s.frames_from(3).unwrap().collect();
409 assert_eq!(kept.len(), 2);
410 }
411
412 #[test]
413 fn drop_up_to_below_oldest_is_noop() {
414 let mut s = ReplicationSource::new(64 * 1024);
415 let _ = s.push_mutation(&argv(&[b"SET", b"k", b"v"]));
416 let _ = s.push_mutation(&argv(&[b"SET", b"k", b"v"]));
417 assert_eq!(s.oldest_offset(), Some(0));
418 s.drop_up_to(0); assert_eq!(s.len(), 2);
420 }
421
422 #[test]
423 fn drop_up_to_at_or_past_newest_drops_everything() {
424 let mut s = ReplicationSource::new(64 * 1024);
425 for _ in 0..3 {
426 let _ = s.push_mutation(&argv(&[b"SET", b"k", b"v"]));
427 }
428 s.drop_up_to(99);
429 assert!(s.is_empty());
430 assert_eq!(s.buffered_bytes(), 0);
431 }
432}