65 lines
1.8 KiB
Rust

//! Retry gate for possibly-transient block-apply failures.
/// Counts consecutive apply failures per block id so the ingest loop can
/// retry before parking.
///
/// A failure streak is keyed to one block id: a failure of a different block
/// starts a fresh streak. Reset only on a genuinely applied block — the
/// `AlreadyApplied` replay of the prefix after a retry cycle must not clear
/// the failing block's streak.
pub struct ApplyRetryGate {
failing: Option<(u64, u32)>,
}
impl ApplyRetryGate {
#[must_use]
pub const fn new() -> Self {
Self { failing: None }
}
/// Registers a failed apply of `block_id`; returns its consecutive
/// attempt count.
pub const fn register_failure(&mut self, block_id: u64) -> u32 {
let attempts = match self.failing {
Some((id, attempts)) if id == block_id => attempts.saturating_add(1),
_ => 1,
};
self.failing = Some((block_id, attempts));
attempts
}
/// Clears the streak; call when a block actually applies.
pub const fn reset(&mut self) {
self.failing = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn counts_consecutive_failures_of_same_block() {
let mut gate = ApplyRetryGate::new();
assert_eq!(gate.register_failure(7), 1);
assert_eq!(gate.register_failure(7), 2);
assert_eq!(gate.register_failure(7), 3);
}
#[test]
fn different_block_starts_fresh_streak() {
let mut gate = ApplyRetryGate::new();
gate.register_failure(7);
gate.register_failure(7);
assert_eq!(gate.register_failure(8), 1);
}
#[test]
fn reset_clears_streak() {
let mut gate = ApplyRetryGate::new();
gate.register_failure(7);
gate.reset();
assert_eq!(gate.register_failure(7), 1);
}
}