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}