Skip to main content

kernel/
backpressure.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//! Admission control: the city-wide shedding posture.
7//! One function serves every queue-shaped resource — signal queues and fd
8//! headroom alike; capacity semantics belong to the caller. Queues and
9//! counters live in storage::queue, never here.
10//! The part `crates/kernel/spec/Backpressure.lean` specifies this module.
11
12/// A queue's occupancy snapshot.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct QueueStats {
15    pub depth: u64,
16    pub capacity: u64,
17}
18
19/// What admitting this item would cost: one slot for a Signal, more for
20/// a new-Run fd reservation.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct ItemMeta {
23    pub cost: u64,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ShedReason {
28    CapacityExhausted,
29}
30
31/// Deliberately exhaustive: a new admission outcome must force every
32/// caller to decide.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Admission {
35    Admit,
36    Shed { reason: ShedReason },
37}
38
39/// Decides whether the queue admits one more item. Pure and total:
40/// `depth + cost <= capacity` admits; checked arithmetic, overflow sheds
41/// (fail-closed). Shedding refuses new items only — starving what is
42/// already queued is a liveness property and citysim's to assert.
43pub fn admit(stats: &QueueStats, item: &ItemMeta) -> Admission {
44    match stats.depth.checked_add(item.cost) {
45        Some(total) if total <= stats.capacity => Admission::Admit,
46        _ => Admission::Shed {
47            reason: ShedReason::CapacityExhausted,
48        },
49    }
50}
51
52#[cfg(kani)]
53mod verification {
54    //! V5: admission is total, monotone — a shallower queue never makes
55    //! the same item harder to admit — and fail-closed when the sum
56    //! overflows. Both harnesses range over every `u64`, and the
57    //! arithmetic is linear, so CBMC returns in under a minute.
58
59    use super::*;
60
61    #[kani::proof]
62    fn admit_is_total_and_monotone_in_depth() {
63        let capacity: u64 = kani::any();
64        let deep: u64 = kani::any();
65        let shallow: u64 = kani::any();
66        kani::assume(shallow <= deep);
67        let item = ItemMeta { cost: kani::any() };
68        let deep_verdict = admit(
69            &QueueStats {
70                depth: deep,
71                capacity,
72            },
73            &item,
74        );
75        let shallow_verdict = admit(
76            &QueueStats {
77                depth: shallow,
78                capacity,
79            },
80            &item,
81        );
82        if deep_verdict == Admission::Admit {
83            assert_eq!(shallow_verdict, Admission::Admit);
84        }
85    }
86
87    /// Fail-closed at the top of the range: whenever admitting the item
88    /// would carry the queue past `u64::MAX`, the answer is Shed. The
89    /// `#[test]` beside it fixes the depth at `u64::MAX`; this holds for
90    /// every depth, capacity and cost whose sum overflows.
91    #[kani::proof]
92    fn an_overflowing_sum_never_admits() {
93        let stats = QueueStats {
94            depth: kani::any(),
95            capacity: kani::any(),
96        };
97        let item = ItemMeta { cost: kani::any() };
98        kani::assume(stats.depth.checked_add(item.cost).is_none());
99        assert_eq!(
100            admit(&stats, &item),
101            Admission::Shed {
102                reason: ShedReason::CapacityExhausted
103            }
104        );
105    }
106}
107
108#[cfg(test)]
109#[allow(
110    clippy::unwrap_used,
111    clippy::expect_used,
112    clippy::panic,
113    clippy::indexing_slicing,
114    reason = "test code"
115)]
116mod tests {
117    use super::*;
118    use proptest::prelude::*;
119
120    #[test]
121    fn admits_up_to_capacity_and_sheds_past_it() {
122        let stats = QueueStats {
123            depth: 9,
124            capacity: 10,
125        };
126        assert_eq!(admit(&stats, &ItemMeta { cost: 1 }), Admission::Admit);
127        assert_eq!(
128            admit(&stats, &ItemMeta { cost: 2 }),
129            Admission::Shed {
130                reason: ShedReason::CapacityExhausted
131            }
132        );
133    }
134
135    #[test]
136    fn overflow_sheds_instead_of_wrapping() {
137        let stats = QueueStats {
138            depth: u64::MAX,
139            capacity: u64::MAX,
140        };
141        assert_eq!(
142            admit(&stats, &ItemMeta { cost: 1 }),
143            Admission::Shed {
144                reason: ShedReason::CapacityExhausted
145            }
146        );
147        // Zero-cost probes still fit a full-but-not-overflowing queue.
148        assert_eq!(admit(&stats, &ItemMeta { cost: 0 }), Admission::Admit);
149    }
150
151    proptest! {
152        /// Kani mirror: monotone in depth.
153        #[test]
154        fn shallower_is_never_harder(capacity in any::<u64>(), deep in any::<u64>(),
155                                     delta in any::<u64>(), cost in any::<u64>()) {
156            let shallow = deep.saturating_sub(delta);
157            let item = ItemMeta { cost };
158            let deep_verdict = admit(&QueueStats { depth: deep, capacity }, &item);
159            let shallow_verdict = admit(&QueueStats { depth: shallow, capacity }, &item);
160            if deep_verdict == Admission::Admit {
161                prop_assert_eq!(shallow_verdict, Admission::Admit);
162            }
163        }
164
165        /// Kani mirror: a sum past `u64::MAX` sheds rather than wraps.
166        #[test]
167        fn an_overflowing_sum_sheds(capacity in any::<u64>(), depth in any::<u64>(),
168                                    cost in any::<u64>()) {
169            if depth.checked_add(cost).is_none() {
170                prop_assert_eq!(
171                    admit(&QueueStats { depth, capacity }, &ItemMeta { cost }),
172                    Admission::Shed { reason: ShedReason::CapacityExhausted }
173                );
174            }
175        }
176    }
177}