Skip to main content

kernel/
retries.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4// Copyright (c) 2026 2youg1 and the sprawling contributors
5
6//! How many times a failed call is made again.
7//! The part `crates/kernel/spec/Retries.lean` specifies this module.
8//!
9//! **One value, one absence.** The figure is a value here and its
10//! absence is [`Retries::UntilHalted`] by construction, so a dispatch,
11//! a model-list probe and the form cannot each read a missing figure
12//! their own way, and no reader can spell a fourth answer.
13//!
14//! It lives in the kernel because two layers read it and neither
15//! depends on the other: the gateway, deciding whether to send the
16//! request again, and the runtime's watchdog, deciding whether to
17//! freeze the run. One home, or the two drift.
18
19/// The ceiling a person set on retrying one endpoint.
20///
21/// Exhaustive rather than a count with a sentinel: "keep trying until
22/// somebody stops this" and "try four times" are different intentions,
23/// and a number cannot spell the first one.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
25pub enum Retries {
26    /// No ceiling. What stops the run is `Halt`, the city's one brake.
27    /// This is what a person who settled nothing asked for.
28    #[default]
29    UntilHalted,
30    /// Give up once this many retries have been spent. Zero is a real
31    /// answer and means "once, then report".
32    AtMost(u32),
33}
34
35impl Retries {
36    /// How many retries a request gets where nothing can halt it.
37    ///
38    /// A person watching a settings page is waiting on one request and
39    /// holds no brake, so `UntilHalted` cannot be honoured literally
40    /// there: a probe that retried forever would hang the page it was
41    /// drawn for. It is honoured as **one** retry, which is what a
42    /// dropped connection costs and what the probe already documented
43    /// as its intent — written here so the reading is the setting's own
44    /// and not a second default at the call site.
45    #[must_use]
46    pub const fn without_a_brake(self) -> u32 {
47        match self {
48            Retries::UntilHalted => 1,
49            Retries::AtMost(ceiling) => ceiling,
50        }
51    }
52
53    /// The figure a record carries, or nothing when no ceiling was set.
54    ///
55    /// Absence on the wire and absence in the ledger are the same fact,
56    /// so both are written by this one reading.
57    #[must_use]
58    pub const fn stated(self) -> Option<u32> {
59        match self {
60            Retries::UntilHalted => None,
61            Retries::AtMost(ceiling) => Some(ceiling),
62        }
63    }
64
65    /// What a stated figure, or the absence of one, means.
66    #[must_use]
67    pub const fn of(stated: Option<u32>) -> Retries {
68        match stated {
69            None => Retries::UntilHalted,
70            Some(ceiling) => Retries::AtMost(ceiling),
71        }
72    }
73}
74
75#[cfg(test)]
76#[allow(
77    clippy::unwrap_used,
78    clippy::expect_used,
79    clippy::panic,
80    reason = "test code"
81)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn absence_is_one_answer_wherever_it_is_read() {
87        assert_eq!(Retries::of(None), Retries::UntilHalted);
88        assert_eq!(Retries::default(), Retries::UntilHalted);
89        assert_eq!(Retries::UntilHalted.stated(), None);
90        // The one reading a probe takes, and it is bounded.
91        assert_eq!(Retries::UntilHalted.without_a_brake(), 1);
92    }
93
94    #[test]
95    fn a_stated_ceiling_survives_the_record_it_is_written_into() {
96        for ceiling in [0_u32, 1, 4, u32::MAX] {
97            let held = Retries::AtMost(ceiling);
98            assert_eq!(Retries::of(held.stated()), held);
99            assert_eq!(held.without_a_brake(), ceiling);
100        }
101    }
102}