use std::collections::VecDeque;
use std::sync::Mutex;
use skippy_protocol::binary::StageWireMessage;
const MAX_TRACKED_REQUESTS: usize = 32;
const MAX_RANGES_PER_REQUEST: usize = 8;
#[derive(Debug)]
struct RequestDiscards {
request_id: u64,
session_id: u64,
ranges: VecDeque<(i32, i32)>,
}
#[derive(Debug, Default)]
pub(super) struct StaleDiscardRegistry {
requests: Mutex<VecDeque<RequestDiscards>>,
}
impl StaleDiscardRegistry {
pub(super) fn record_message(&self, message: &StageWireMessage) {
let (Some(&min_id), Some(&max_id)) = (message.tokens.first(), message.tokens.get(1)) else {
return;
};
if min_id > max_id {
return;
}
self.record(message.request_id, message.session_id, min_id, max_id);
}
pub(super) fn record(&self, request_id: u64, session_id: u64, min_id: i32, max_id: i32) {
let mut requests = self.requests.lock().expect("stale discard lock poisoned");
if let Some(entry) = requests
.iter_mut()
.find(|entry| entry.request_id == request_id && entry.session_id == session_id)
{
if entry.ranges.len() >= MAX_RANGES_PER_REQUEST {
entry.ranges.pop_front();
}
entry.ranges.push_back((min_id, max_id));
return;
}
if requests.len() >= MAX_TRACKED_REQUESTS {
requests.pop_front();
}
let mut ranges = VecDeque::new();
ranges.push_back((min_id, max_id));
requests.push_back(RequestDiscards {
request_id,
session_id,
ranges,
});
}
pub(super) fn is_discarded(&self, request_id: u64, session_id: u64, window_id: i32) -> bool {
let requests = self.requests.lock().expect("stale discard lock poisoned");
requests
.iter()
.filter(|entry| entry.request_id == request_id && entry.session_id == session_id)
.any(|entry| {
entry
.ranges
.iter()
.any(|&(min_id, max_id)| (min_id..=max_id).contains(&window_id))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn records_and_matches_ranges_per_request() {
let registry = StaleDiscardRegistry::default();
registry.record(7, 9, 10, 14);
assert!(registry.is_discarded(7, 9, 10));
assert!(registry.is_discarded(7, 9, 14));
assert!(!registry.is_discarded(7, 9, 15));
assert!(!registry.is_discarded(7, 9, 9));
assert!(!registry.is_discarded(8, 9, 12));
assert!(!registry.is_discarded(7, 10, 12));
}
#[test]
fn range_count_is_bounded_per_request() {
let registry = StaleDiscardRegistry::default();
for index in 0..(MAX_RANGES_PER_REQUEST as i32 + 4) {
registry.record(1, 1, index * 10, index * 10 + 1);
}
assert!(!registry.is_discarded(1, 1, 0));
let newest = (MAX_RANGES_PER_REQUEST as i32 + 3) * 10;
assert!(registry.is_discarded(1, 1, newest));
}
#[test]
fn tracked_request_count_is_bounded() {
let registry = StaleDiscardRegistry::default();
for request in 0..(MAX_TRACKED_REQUESTS as u64 + 4) {
registry.record(request, 1, 0, 10);
}
assert!(!registry.is_discarded(0, 1, 5));
assert!(registry.is_discarded(MAX_TRACKED_REQUESTS as u64 + 3, 1, 5));
}
#[test]
fn malformed_discard_messages_are_ignored() {
use skippy_protocol::binary::{StageStateHeader, WireMessageKind};
let registry = StaleDiscardRegistry::default();
let mut message = StageWireMessage {
kind: WireMessageKind::DiscardStaleWindows,
pos_start: 0,
token_count: 0,
state: StageStateHeader::new(WireMessageKind::DiscardStaleWindows),
request_id: 1,
session_id: 1,
sampling: None,
chat_sampling_metadata: None,
tokens: Vec::new(),
positions: Vec::new(),
activation: Vec::new(),
raw_bytes: Vec::new(),
};
registry.record_message(&message);
assert!(!registry.is_discarded(1, 1, 0));
message.tokens = vec![9, 3];
registry.record_message(&message);
assert!(!registry.is_discarded(1, 1, 5));
message.tokens = vec![3, 9];
registry.record_message(&message);
assert!(registry.is_discarded(1, 1, 5));
}
}