use serde::{Deserialize, Serialize};
use super::protocol::{BandConfig, DRAIN_SUSPECT_FRACTION};
#[derive(Debug, Clone)]
pub struct WindowController {
min_samples: usize,
min_wall_s: f64,
issued: usize,
closed_at_s: Option<f64>,
last_completion_s: f64,
in_flight: usize,
peak_in_flight: usize,
}
impl WindowController {
#[must_use]
pub fn new(config: &BandConfig) -> Self {
Self {
min_samples: config.min_samples,
min_wall_s: config.min_wall_clock.as_secs_f64(),
issued: 0,
closed_at_s: None,
last_completion_s: 0.0,
in_flight: 0,
peak_in_flight: 0,
}
}
#[must_use]
pub fn with_bounds(min_samples: usize, min_wall_s: f64) -> Self {
Self {
min_samples,
min_wall_s,
issued: 0,
closed_at_s: None,
last_completion_s: 0.0,
in_flight: 0,
peak_in_flight: 0,
}
}
pub fn try_admit(&mut self, now_s: f64) -> Option<usize> {
self.try_admit_with_in_flight(now_s).map(|(index, _)| index)
}
pub fn try_admit_with_in_flight(&mut self, now_s: f64) -> Option<(usize, usize)> {
if self.closed_at_s.is_some() {
return None;
}
if self.issued >= self.min_samples && now_s >= self.min_wall_s {
self.closed_at_s = Some(now_s);
return None;
}
let index = self.issued;
self.issued += 1;
self.in_flight += 1;
self.peak_in_flight = self.peak_in_flight.max(self.in_flight);
Some((index, self.in_flight))
}
pub fn complete(&mut self, now_s: f64) -> bool {
self.in_flight = self.in_flight.saturating_sub(1);
self.last_completion_s = self.last_completion_s.max(now_s);
self.closed_at_s.is_some_and(|t| now_s > t)
}
#[must_use]
pub fn peak_in_flight(&self) -> usize {
self.peak_in_flight
}
#[must_use]
pub fn issued(&self) -> usize {
self.issued
}
#[must_use]
pub fn in_flight(&self) -> usize {
self.in_flight
}
#[must_use]
pub fn is_closed(&self) -> bool {
self.closed_at_s.is_some()
}
#[must_use]
pub fn report(&self) -> WindowReport {
let close = self.closed_at_s.unwrap_or(self.last_completion_s);
let drain_ms = ((self.last_completion_s - close).max(0.0)) * 1000.0;
let window_ms = close * 1000.0;
let mut suspect = Vec::new();
if window_ms > 0.0 && drain_ms > DRAIN_SUSPECT_FRACTION * window_ms {
suspect.push(format!(
"§4.4.7 drain_ms={drain_ms:.1} > 0.5 x window_ms={window_ms:.1}: one request \
dominated the window; re-run this band with a longer window"
));
}
if self.closed_at_s.is_none() {
suspect.push(
"§4.4.2 the window never closed: neither termination bound was reached, so this \
band did not run the protocol"
.to_string(),
);
}
WindowReport {
requested: self.issued,
window_ms,
drain_ms,
client_peak_in_flight: self.peak_in_flight,
suspect,
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct WindowReport {
pub requested: usize,
pub window_ms: f64,
pub drain_ms: f64,
pub client_peak_in_flight: usize,
pub suspect: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn sample_bound_alone_does_not_close_the_window() {
let mut w = WindowController::with_bounds(3, 60.0);
for i in 0..3 {
assert_eq!(w.try_admit(0.1 * f64::from(i)), Some(i as usize));
}
assert_eq!(w.try_admit(0.4), Some(3), "still open: 0.4s < 60s");
assert!(!w.is_closed());
}
#[test]
fn wall_clock_bound_alone_does_not_close_the_window() {
let mut w = WindowController::with_bounds(30, 1.0);
for _ in 0..4 {
assert!(
w.try_admit(100.0).is_some(),
"still open: only 4 of 30 samples"
);
}
assert!(!w.is_closed());
}
#[test]
fn window_closes_only_when_both_bounds_are_satisfied() {
let mut w = WindowController::with_bounds(3, 10.0);
assert!(w.try_admit(0.0).is_some());
assert!(w.try_admit(1.0).is_some());
assert!(w.try_admit(2.0).is_some());
assert!(!w.is_closed());
assert_eq!(w.try_admit(10.0), None, "3 samples AND 10s -> closed");
assert!(w.is_closed());
}
#[test]
fn no_new_request_is_admitted_at_or_after_t() {
let mut w = WindowController::with_bounds(1, 1.0);
assert!(w.try_admit(0.0).is_some());
assert_eq!(w.try_admit(1.0), None);
assert_eq!(w.try_admit(1.0001), None);
assert_eq!(w.try_admit(500.0), None);
assert_eq!(w.issued(), 1, "exactly one request was ever admitted");
}
#[test]
fn drain_ms_is_measured_from_window_close() {
let mut w = WindowController::with_bounds(2, 4.0);
assert!(w.try_admit(0.0).is_some());
assert!(w.try_admit(1.0).is_some());
assert!(!w.complete(2.0), "completed inside the window");
assert_eq!(w.try_admit(4.0), None, "T = 4.0");
assert!(w.complete(5.5), "completed during the drain");
let r = w.report();
assert!((r.window_ms - 4000.0).abs() < 1e-9, "{}", r.window_ms);
assert!((r.drain_ms - 1500.0).abs() < 1e-9, "{}", r.drain_ms);
assert!(r.suspect.is_empty(), "{:?}", r.suspect);
}
#[test]
fn a_dominating_request_is_annotated_suspect() {
let mut w = WindowController::with_bounds(1, 2.0);
assert!(w.try_admit(0.0).is_some());
assert_eq!(w.try_admit(2.0), None);
assert!(w.complete(20.0));
let r = w.report();
assert!((r.drain_ms - 18000.0).abs() < 1e-9);
assert_eq!(r.suspect.len(), 1, "{:?}", r.suspect);
assert!(r.suspect[0].contains("drain_ms"));
}
#[test]
fn an_unclosed_window_is_suspect_not_clean() {
let mut w = WindowController::with_bounds(100, 60.0);
assert!(w.try_admit(0.0).is_some());
assert!(!w.complete(1.0));
let r = w.report();
assert_eq!(r.drain_ms, 0.0);
assert!(
r.suspect.iter().any(|s| s.contains("never closed")),
"{:?}",
r.suspect
);
}
#[test]
fn peak_in_flight_tracks_concurrent_admissions() {
let mut w = WindowController::with_bounds(100, 100.0);
for _ in 0..8 {
assert!(w.try_admit(0.0).is_some());
}
assert_eq!(w.in_flight(), 8);
assert_eq!(w.peak_in_flight(), 8);
for _ in 0..8 {
w.complete(1.0);
}
assert_eq!(w.in_flight(), 0);
assert_eq!(w.peak_in_flight(), 8, "peak is a high-water mark");
}
#[test]
fn controller_reads_its_bounds_from_the_band_config() {
let cfg = BandConfig::conformant(8);
let w = WindowController::new(&cfg);
assert_eq!(w.min_samples, 64, "max(30, 8*8)");
assert!((w.min_wall_s - 60.0).abs() < 1e-9);
assert_eq!(cfg.quiesce, Duration::from_secs(5));
}
}
#[must_use]
pub fn closed_loop_probe(
concurrency: usize,
requests: usize,
work: std::time::Duration,
) -> (usize, std::time::Duration) {
use std::sync::Mutex;
use std::time::Instant;
let controller = Mutex::new(WindowController::with_bounds(requests, 0.0));
let origin = Instant::now();
std::thread::scope(|scope| {
for _ in 0..concurrency {
scope.spawn(|| loop {
let admitted = {
let mut c = controller
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
c.try_admit(origin.elapsed().as_secs_f64())
};
if admitted.is_none() {
break;
}
std::thread::sleep(work);
let mut c = controller
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
c.complete(origin.elapsed().as_secs_f64());
});
}
});
let peak = controller
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.peak_in_flight();
(peak, origin.elapsed())
}
#[cfg(test)]
mod concurrency_proof_tests {
use super::*;
use std::time::Duration;
#[test]
fn eight_workers_are_actually_concurrent() {
let (peak, _) = closed_loop_probe(8, 64, Duration::from_millis(20));
assert_eq!(
peak, 8,
"peak in-flight must reach c; a serialising client gives 1"
);
}
#[test]
fn wall_time_at_c8_is_not_eight_times_the_c1_time() {
let requests = 32;
let work = Duration::from_millis(20);
let (peak1, wall1) = closed_loop_probe(1, requests, work);
let (peak8, wall8) = closed_loop_probe(8, requests, work);
assert_eq!(peak1, 1);
assert_eq!(peak8, 8);
let speedup = wall1.as_secs_f64() / wall8.as_secs_f64();
eprintln!(
"closed_loop_probe: {requests} requests x {work:?} -- \
c=1 peak={peak1} wall={wall1:?}; c=8 peak={peak8} wall={wall8:?}; speedup={speedup:.2}x"
);
assert!(
speedup > 4.0,
"c=1 took {wall1:?}, c=8 took {wall8:?} (speedup {speedup:.2}x); \
a concurrent client must be much faster, a sequential one scores ~1.0x"
);
}
#[test]
fn the_probe_reports_one_for_a_sequential_client() {
let (peak, wall) = closed_loop_probe(1, 8, Duration::from_millis(10));
assert_eq!(peak, 1);
assert!(
wall >= Duration::from_millis(70),
"8 x 10ms sequential must take ~80ms, got {wall:?}"
);
}
}