psx_residency/backoff.rs
1//! Retry backoff for failed reads.
2//!
3//! Lifted from the grid scheduler (`stream_retry_backoff_windows`): the first
4//! retry waits `base_windows` scheduling windows, every further consecutive
5//! failure doubles the wait up to `base_windows << max_shift`. A success
6//! resets the count, so a transient drive hiccup recovers fast while a
7//! permanently bad chunk settles into one retry every few seconds instead of
8//! one per frame. A resource is never abandoned.
9
10/// Backoff schedule, in scheduling windows (one window per
11/// [`crate::Residency::step`] call).
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub struct Backoff {
14 /// Wait after the first failure.
15 pub base_windows: u32,
16 /// The wait doubles per failure at most this many times.
17 pub max_shift: u32,
18}
19
20impl Backoff {
21 /// The grid scheduler's schedule: 16 windows, doubling up to 16 << 5 = 512
22 /// (about 0.27 s to 8.5 s when a window is one 60 Hz frame).
23 pub const GRID_DEFAULT: Backoff = Backoff {
24 base_windows: 16,
25 max_shift: 5,
26 };
27
28 /// Windows to wait after `failure_count` consecutive failures (0 when the
29 /// count is 0).
30 pub const fn windows(self, failure_count: u8) -> u32 {
31 if failure_count == 0 {
32 return 0;
33 }
34 let shift = failure_count as u32 - 1;
35 let shift = if shift > self.max_shift {
36 self.max_shift
37 } else {
38 shift
39 };
40 self.base_windows << shift
41 }
42}
43
44impl Default for Backoff {
45 fn default() -> Self {
46 Self::GRID_DEFAULT
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn doubles_per_failure_up_to_the_cap() {
56 let backoff = Backoff::GRID_DEFAULT;
57 assert_eq!(backoff.windows(0), 0);
58 assert_eq!(backoff.windows(1), 16);
59 assert_eq!(backoff.windows(2), 32);
60 assert_eq!(backoff.windows(5), 256);
61 assert_eq!(backoff.windows(6), 512);
62 assert_eq!(backoff.windows(7), 512);
63 assert_eq!(backoff.windows(255), 512);
64 }
65}