use std::collections::{BTreeSet, VecDeque};
use plaza_client_utils::ack::AckWindow;
use crate::relevance::SetDigest;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RecoveryPolicy {
Naive,
AckRecovery,
}
#[derive(Clone, Debug, Default)]
pub struct DeltaPlan {
pub full_baseline: bool,
pub baseline_seq: Option<u64>,
pub entered: Vec<u64>,
pub left: Vec<u64>,
}
#[derive(Clone, Debug)]
struct FlowControl {
stalled_after: u64,
keepalive_every: u64,
last_ack: Option<u64>,
last_keepalive: u64,
}
#[derive(Clone, Debug)]
pub struct DeltaBaseline {
sent: VecDeque<(u64, BTreeSet<u64>)>,
acked: Option<(u64, BTreeSet<u64>)>,
assumed_held: BTreeSet<u64>,
last_sent: BTreeSet<u64>,
last_sent_seq: Option<u64>,
history: usize,
policy: RecoveryPolicy,
needs_full: bool,
full_rebuilds: u64,
unacked: usize,
last_ack_window: Option<(u64, u64)>,
flow: Option<FlowControl>,
}
impl DeltaBaseline {
pub fn new(history: usize) -> Self {
Self {
sent: VecDeque::with_capacity(history.max(1)),
acked: None,
assumed_held: BTreeSet::new(),
last_sent: BTreeSet::new(),
last_sent_seq: None,
history: history.max(1),
policy: RecoveryPolicy::AckRecovery,
needs_full: true,
full_rebuilds: 0,
unacked: 0,
last_ack_window: None,
flow: None,
}
}
pub fn with_policy(mut self, policy: RecoveryPolicy) -> Self {
self.policy = policy;
self
}
pub fn with_flow(mut self, stalled_after: u64, keepalive_every: u64) -> Self {
self.flow = Some(FlowControl {
stalled_after,
keepalive_every: keepalive_every.max(1),
last_ack: None,
last_keepalive: 0,
});
self
}
pub fn stalled(&self, now: u64) -> bool {
let Some(flow) = &self.flow else {
return false;
};
let Some(last_ack) = flow.last_ack else {
return false;
};
now.saturating_sub(last_ack) > flow.stalled_after
}
pub fn should_send(&mut self, now: u64) -> bool {
let Some(flow) = &mut self.flow else {
return true;
};
if flow.last_ack.is_none() {
flow.last_ack = Some(now);
return true;
}
if !self.stalled(now) {
return true;
}
let flow = self.flow.as_mut().expect("checked above");
if now.saturating_sub(flow.last_keepalive) >= flow.keepalive_every {
flow.last_keepalive = now;
return true;
}
false
}
pub fn set_policy(&mut self, policy: RecoveryPolicy) {
if policy != self.policy {
self.policy = policy;
self.reset();
}
}
pub fn reset(&mut self) {
self.sent.clear();
self.acked = None;
self.assumed_held.clear();
self.last_sent.clear();
self.last_sent_seq = None;
self.needs_full = true;
self.unacked = 0;
self.last_ack_window = None;
if let Some(flow) = &mut self.flow {
flow.last_ack = None;
flow.last_keepalive = 0;
}
}
pub fn plan(&mut self, current: &BTreeSet<u64>, seq: u64) -> DeltaPlan {
let recovering = self.policy == RecoveryPolicy::AckRecovery;
let stale = recovering && self.baseline_is_stale();
let unknown = recovering && self.acked.is_none();
let rebuild = self.needs_full || stale;
let full_baseline = rebuild || unknown;
self.needs_full = false;
if rebuild {
self.acked = None;
self.assumed_held.clear();
self.last_sent.clear();
self.sent.clear();
self.full_rebuilds += 1;
}
let mut plan = DeltaPlan {
full_baseline,
..DeltaPlan::default()
};
match (&self.acked, recovering) {
(Some((acked_seq, acked)), true) => {
let mut send_baseline = acked.clone();
self.assumed_held.clone_from(acked);
for (sent_seq, state) in &self.sent {
if sent_seq > acked_seq {
send_baseline.retain(|key| state.contains(key));
self.assumed_held.extend(state.iter().copied());
}
}
plan.baseline_seq = Some(*acked_seq);
plan.entered = current.difference(&send_baseline).copied().collect();
plan.left = self.assumed_held.difference(current).copied().collect();
}
_ if full_baseline => {
plan.baseline_seq = None;
plan.entered = current.iter().copied().collect();
}
_ => {
plan.baseline_seq = self.last_sent_seq;
plan.entered = current.difference(&self.last_sent).copied().collect();
plan.left = self.last_sent.difference(current).copied().collect();
}
}
self.sent.push_back((seq, current.clone()));
while self.sent.len() > self.history {
self.sent.pop_front();
}
self.last_sent.clone_from(current);
self.last_sent_seq = Some(seq);
self.recount_unacked();
plan
}
pub fn observe_ack(&mut self, newest: u64, mask: u64, digest: u64) {
if self.policy != RecoveryPolicy::AckRecovery {
return;
}
let window = AckWindow::from_encoded(newest, mask);
let first = self
.acked
.as_ref()
.map(|(seq, _)| *seq + 1)
.or_else(|| self.sent.front().map(|(seq, _)| *seq));
if let Some(first) = first
&& let Some(base) = window.contiguous_base(first)
&& let Some((seq, state)) = self.sent.iter().find(|(seq, _)| *seq == base)
{
self.acked = Some((*seq, state.clone()));
}
if let Some((acked_seq, acked)) = &self.acked
&& *acked_seq == newest
&& SetDigest::from_keys(acked.iter().copied()).digest() != digest
{
self.needs_full = true;
}
self.last_ack_window = Some((newest, mask));
self.recount_unacked();
}
fn recount_unacked(&mut self) {
self.unacked = match self.last_ack_window {
Some((newest, mask)) => {
let window = AckWindow::from_encoded(newest, mask);
self.sent.iter().filter(|(seq, _)| !window.contains(*seq)).count()
}
None => self.sent.len(),
};
}
pub fn observe_ack_at(&mut self, newest: u64, mask: u64, digest: u64, now: u64) {
let resuming = self.stalled(now);
if let Some(flow) = &mut self.flow {
flow.last_ack = Some(now);
}
if resuming {
self.reset();
if let Some(flow) = &mut self.flow {
flow.last_ack = Some(now);
}
return;
}
self.observe_ack(newest, mask, digest);
}
pub fn request_full_baseline(&mut self) {
self.needs_full = true;
}
pub fn full_rebuilds(&self) -> u64 {
self.full_rebuilds
}
pub fn unacked(&self) -> usize {
self.unacked
}
pub fn acked_seq(&self) -> Option<u64> {
self.acked.as_ref().map(|(seq, _)| *seq)
}
fn baseline_is_stale(&self) -> bool {
let Some((acked_seq, _)) = &self.acked else {
return false;
};
self.sent.len() >= self.history && self.sent.front().is_some_and(|(oldest, _)| *acked_seq < *oldest)
}
}
#[cfg(test)]
mod tests {
use super::*;
mod what_nothing_was_calling {
use super::*;
#[test]
fn changing_the_policy_throws_away_the_acknowledged_baseline() {
let mut baseline = DeltaBaseline::new(8).with_policy(RecoveryPolicy::AckRecovery);
let world = keys(&[1, 2, 3]);
baseline.plan(&world, 1);
baseline.observe_ack(1, 1, digest(&world));
assert_eq!(baseline.acked_seq(), Some(1), "there is a baseline to lose");
let before = baseline.full_rebuilds();
baseline.set_policy(RecoveryPolicy::Naive);
assert_eq!(baseline.acked_seq(), None, "the acknowledged state is gone");
let plan = baseline.plan(&world, 2);
assert!(plan.full_baseline, "so the next plan is a rebuild");
assert!(baseline.full_rebuilds() > before);
}
#[test]
fn setting_the_policy_it_already_has_changes_nothing() {
let mut baseline = DeltaBaseline::new(8).with_policy(RecoveryPolicy::AckRecovery);
let world = keys(&[1, 2, 3]);
baseline.plan(&world, 1);
baseline.observe_ack(1, 1, digest(&world));
baseline.set_policy(RecoveryPolicy::AckRecovery);
assert_eq!(baseline.acked_seq(), Some(1), "still acknowledged");
assert!(!baseline.plan(&world, 2).full_baseline, "and still a delta");
}
#[test]
fn unacked_counts_what_is_still_in_flight() {
let mut baseline = DeltaBaseline::new(8);
let world = keys(&[1, 2, 3]);
for seq in 1..=3 {
baseline.plan(&world, seq);
}
let behind = baseline.unacked();
assert!(behind > 0, "three sent and none acknowledged: {behind}");
baseline.observe_ack(3, 0b111, digest(&world));
assert!(
baseline.unacked() < behind,
"acknowledging has to reduce it: {} against {behind}",
baseline.unacked()
);
}
}
fn keys(items: &[u64]) -> BTreeSet<u64> {
items.iter().copied().collect()
}
fn digest(items: &BTreeSet<u64>) -> u64 {
SetDigest::from_keys(items.iter().copied()).digest()
}
#[derive(Default)]
struct Mirror {
held: BTreeSet<u64>,
}
impl Mirror {
fn apply(&mut self, plan: &DeltaPlan) {
if plan.full_baseline {
self.held.clear();
}
for key in &plan.left {
self.held.remove(key);
}
for key in &plan.entered {
self.held.insert(*key);
}
}
}
#[test]
fn a_rebuild_starts_a_new_epoch_and_one_ack_round_trip_ends_the_full_sets() {
let mut b = DeltaBaseline::new(24);
let world = keys(&[1, 2, 3]);
let digest = SetDigest::from_keys(world.iter().copied()).digest();
for seq in 1..=5 {
b.plan(&world, seq);
b.observe_ack(seq, u64::MAX, digest);
}
b.request_full_baseline();
assert!(b.plan(&world, 6).full_baseline);
b.observe_ack(5, u64::MAX, digest);
assert_eq!(b.acked_seq(), None, "a pre-rebuild state is not a baseline for the new epoch");
b.observe_ack(6, u64::MAX, digest);
let settled = b.plan(&world, 7);
assert!(!settled.full_baseline, "one acknowledged round trip ends the rebuild churn");
assert!(settled.entered.is_empty(), "and the stream is deltas again");
}
#[test]
fn a_silent_subscriber_is_throttled_to_keepalives_and_one_ack_restores_it() {
let mut b = DeltaBaseline::new(24).with_flow(3_000, 1_000);
let world = keys(&[1, 2, 3]);
let digest = SetDigest::from_keys(world.iter().copied()).digest();
let mut now = 0u64;
let mut seq = 0u64;
let mut sent_while_stalled = 0;
for _ in 0..160 {
now += 62;
if b.should_send(now) {
seq += 1;
b.plan(&world, seq);
if now <= 1_000 {
b.observe_ack_at(seq, u64::MAX, digest, now);
} else if now > 5_000 {
sent_while_stalled += 1;
}
}
}
assert!(b.stalled(now), "three silent seconds is stalled");
assert!(
(3..=7).contains(&sent_while_stalled),
"a stalled subscriber gets about one keepalive a second: {sent_while_stalled} in 5 s"
);
b.observe_ack_at(seq, u64::MAX, digest, now);
assert!(!b.stalled(now));
assert!(b.should_send(now + 62), "an acknowledged subscriber is streamed to again");
let fresh = b.plan(&world, seq + 1);
assert!(fresh.full_baseline, "the resumed subscriber starts from one clean full set");
b.observe_ack_at(seq + 1, u64::MAX, digest, now + 62);
let settled = b.plan(&world, seq + 2);
assert!(!settled.full_baseline, "and is back to deltas one round trip later");
assert!(settled.entered.is_empty(), "with nothing spuriously re-sent");
}
#[test]
fn without_flow_control_every_round_sends() {
let mut b = DeltaBaseline::new(24);
for round in 0..100u64 {
assert!(b.should_send(round * 62), "flow control is opt-in");
}
}
#[test]
fn a_fresh_subscriber_is_not_born_stalled() {
let mut b = DeltaBaseline::new(24).with_flow(3_000, 1_000);
assert!(b.should_send(3_600_000));
assert!(!b.stalled(3_600_000));
assert!(b.should_send(3_600_062), "full rate through the grace period");
b.observe_ack_at(1, u64::MAX, 0, 3_600_062);
b.reset();
assert!(b.should_send(7_200_000), "the new occupant's silence starts now");
assert!(!b.stalled(7_200_000));
}
#[test]
fn a_fresh_subscriber_is_sent_the_whole_set_not_a_difference() {
let mut b = DeltaBaseline::new(24);
let world = keys(&[1, 2, 3, 4, 5]);
for seq in 0..50 {
b.plan(&world, seq);
}
b.reset();
let mut mirror = Mirror::default();
let plan = b.plan(&world, 50);
mirror.apply(&plan);
assert!(plan.full_baseline, "a fresh subscriber must be told to start from nothing");
assert_eq!(mirror.held, world, "and must receive the whole visible set at once");
}
#[test]
fn a_reused_slot_without_a_reset_hands_the_new_occupant_the_old_ones_state() {
let world = keys(&[1, 2, 3, 4, 5]);
let mut b = DeltaBaseline::new(24);
let mut previous = Mirror::default();
previous.apply(&b.plan(&world, 0));
b.observe_ack(0, 1, digest(&previous.held));
let mut joiner = Mirror::default();
joiner.apply(&b.plan(&world, 1));
assert!(joiner.held.is_empty(), "this is the bug: the joiner is sent nothing at all");
b.reset();
let mut joiner = Mirror::default();
joiner.apply(&b.plan(&world, 2));
assert_eq!(joiner.held, world, "a reset makes the next plan a full baseline");
}
#[test]
fn a_lost_packet_is_re_derived_by_the_next_difference() {
let mut b = DeltaBaseline::new(24);
let mut mirror = Mirror::default();
mirror.apply(&b.plan(&keys(&[1, 2]), 0));
b.observe_ack(0, 1, digest(&mirror.held));
let _lost = b.plan(&keys(&[1, 2, 3]), 1);
let plan = b.plan(&keys(&[1, 2, 3]), 2);
mirror.apply(&plan);
assert_eq!(mirror.held, keys(&[1, 2, 3]), "the lost key was re-sent, not lost forever");
}
#[test]
fn the_naive_policy_loses_a_lost_packets_contents_forever() {
let mut b = DeltaBaseline::new(24).with_policy(RecoveryPolicy::Naive);
let mut mirror = Mirror::default();
mirror.apply(&b.plan(&keys(&[1, 2]), 0));
let _lost = b.plan(&keys(&[1, 2, 3]), 1);
let plan = b.plan(&keys(&[1, 2, 3]), 2);
mirror.apply(&plan);
assert!(!mirror.held.contains(&3), "the naive policy never mentions key 3 again");
}
#[test]
fn a_drifted_mirror_is_caught_by_its_digest_and_rebuilt() {
let mut b = DeltaBaseline::new(24);
let mut mirror = Mirror::default();
let world = keys(&[1, 2, 3, 4]);
mirror.apply(&b.plan(&world, 0));
b.observe_ack(0, 1, digest(&mirror.held));
mirror.held.remove(&3);
b.observe_ack(0, 1, digest(&mirror.held));
let plan = b.plan(&world, 1);
mirror.apply(&plan);
assert!(plan.full_baseline, "the digest disagreement must force a rebuild");
assert_eq!(mirror.held, world, "and the rebuild must restore the mirror exactly");
assert_eq!(b.full_rebuilds(), 2, "the first baseline and this repair");
}
#[test]
fn a_matching_digest_never_forces_a_rebuild() {
let mut b = DeltaBaseline::new(24);
let mut mirror = Mirror::default();
let world = keys(&[1, 2, 3]);
mirror.apply(&b.plan(&world, 0));
b.observe_ack(0, 1, digest(&mirror.held));
for seq in 1..30 {
let plan = b.plan(&world, seq);
mirror.apply(&plan);
assert!(!plan.full_baseline, "a healthy stream never rebuilds (seq {seq})");
b.observe_ack(seq, u64::MAX, digest(&mirror.held));
}
}
#[test]
fn a_key_that_enters_and_leaves_inside_the_gap_is_still_retracted() {
let mut b = DeltaBaseline::new(24);
let mut mirror = Mirror::default();
mirror.apply(&b.plan(&keys(&[1]), 0));
b.observe_ack(0, 1, digest(&mirror.held));
mirror.apply(&b.plan(&keys(&[1, 9]), 1));
let plan = b.plan(&keys(&[1]), 2);
mirror.apply(&plan);
assert_eq!(mirror.held, keys(&[1]), "the transient key was retracted");
}
#[test]
fn a_baseline_older_than_the_history_forces_a_rebuild() {
let mut b = DeltaBaseline::new(4);
let mut mirror = Mirror::default();
let world = keys(&[1, 2, 3]);
mirror.apply(&b.plan(&world, 0));
b.observe_ack(0, 1, digest(&mirror.held));
let mut rebuilt = false;
for seq in 1..12 {
let plan = b.plan(&world, seq);
mirror.apply(&plan);
rebuilt |= plan.full_baseline;
}
assert!(rebuilt, "an un-re-derivable baseline has to be rebuilt, not stepped over");
assert_eq!(mirror.held, world, "and the subscriber ends up correct");
}
#[test]
fn acknowledging_a_packet_after_a_gap_does_not_move_the_baseline_past_it() {
let mut b = DeltaBaseline::new(24);
let mut mirror = Mirror::default();
mirror.apply(&b.plan(&keys(&[1]), 0));
b.observe_ack(0, 1, digest(&mirror.held));
let _lost = b.plan(&keys(&[1, 2]), 1); mirror.apply(&b.plan(&keys(&[1, 2]), 2));
let window = {
let mut w = AckWindow::new();
w.observe(0);
w.observe(2);
w
};
let (newest, mask) = window.encode().expect("a window with two packets encodes");
b.observe_ack(newest, mask, digest(&mirror.held));
assert_eq!(b.acked_seq(), Some(0), "the frontier stops at the gap");
}
#[test]
fn a_long_lossy_run_still_converges() {
let mut b = DeltaBaseline::new(16);
let mut mirror = Mirror::default();
let mut window = AckWindow::new();
let mut rng: u64 = 0x1234_5678;
for seq in 0..400u64 {
let world: BTreeSet<u64> = (0..24u64).filter(|k| (seq / (k + 2)) % 3 != 0).collect();
let plan = b.plan(&world, seq);
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
let delivered = !(rng >> 33).is_multiple_of(4);
if delivered {
mirror.apply(&plan);
window.observe(seq);
if let Some((newest, mask)) = window.encode() {
b.observe_ack(newest, mask, digest(&mirror.held));
}
}
if delivered && seq > 40 {
assert_eq!(mirror.held, world, "mirror diverged at seq {seq}");
}
}
}
}