Skip to main content

kernel/
keep_warm.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//! Keep-warm: whether, and when, a prompt cache still in use is renewed
7//! before the provider lets it expire.
8//! The part `crates/kernel/spec/KeepWarm.lean` specifies this module.
9//!
10//! **Off unless the city enables it.** A renewal is a request the person
11//! pays for, so [`KeepWarm::Off`] is the default and plans nothing. Spend
12//! is observed where every request's usage is recorded; this decision
13//! reads no balance and refuses nothing.
14
15use crate::consts_external::{PROMPT_CACHE_RENEWAL_MARGIN_SECS, PROMPT_CACHE_TTL_SECS};
16use serde::{Deserialize, Serialize};
17
18/// Whether this city renews a warm prompt cache before it expires.
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
22pub enum KeepWarm {
23    /// No renewal is ever planned: the city sends no request of its own.
24    #[default]
25    Off,
26    /// A prefix a real request carried within the last cache lifetime is
27    /// renewed once, just before that lifetime ends.
28    FiveMinute,
29}
30
31/// One prefix's cache as last seen: when a real request carried it, in
32/// milliseconds, and whether that use still owes its one renewal.
33///
34/// The renewal is recorded rather than inferred from the times, so the
35/// bound of one renewal per real use holds whatever lead a caller
36/// passes (kernel D62).
37#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct CacheUse {
39    used_ms: u64,
40    renewal: Renewal,
41}
42
43/// Whether a real use has had its renewal.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45enum Renewal {
46    Owed,
47    Spent,
48}
49
50impl CacheUse {
51    /// A real request carrying the prefix was sent at `at_ms`; the use
52    /// owes one renewal.
53    pub fn sent(at_ms: u64) -> CacheUse {
54        CacheUse {
55            used_ms: at_ms,
56            renewal: Renewal::Owed,
57        }
58    }
59
60    /// The renewal this use owed has been sent; the use owes none until
61    /// the next real request.
62    #[must_use]
63    pub fn renewed(self) -> CacheUse {
64        CacheUse {
65            renewal: Renewal::Spent,
66            ..self
67        }
68    }
69}
70
71/// The instant the next renewal request is sent, or `None` when none is.
72///
73/// `lead_ms` is how long a request to the caller's endpoint takes from
74/// leaving the city to the provider starting it, as the caller measured
75/// it. The renewal is due that long, plus
76/// [`PROMPT_CACHE_RENEWAL_MARGIN_SECS`], before the lifetime the real
77/// use started ends, so a renewal no slower than the lead starts while
78/// the entry is still alive. A use whose renewal was sent plans none.
79pub fn renewal_due(setting: KeepWarm, cache: CacheUse, lead_ms: u64) -> Option<u64> {
80    match (setting, cache.renewal) {
81        (KeepWarm::Off, Renewal::Owed | Renewal::Spent)
82        | (KeepWarm::FiveMinute, Renewal::Spent) => None,
83        (KeepWarm::FiveMinute, Renewal::Owed) => Some(
84            cache
85                .used_ms
86                .checked_add(PROMPT_CACHE_TTL_SECS.checked_mul(1000)?)?
87                .saturating_sub(PROMPT_CACHE_RENEWAL_MARGIN_SECS.checked_mul(1000)?)
88                .saturating_sub(lead_ms),
89        ),
90    }
91}
92
93#[cfg(test)]
94#[allow(clippy::arithmetic_side_effects, reason = "test code")]
95mod tests {
96    use super::*;
97    use proptest::prelude::*;
98
99    const TTL_MS: u64 = PROMPT_CACHE_TTL_SECS * 1000;
100    const MARGIN_MS: u64 = PROMPT_CACHE_RENEWAL_MARGIN_SECS * 1000;
101    const LEAD_MS: u64 = 2_000;
102
103    #[test]
104    fn the_default_setting_plans_no_request_of_its_own() {
105        let use_at = 1_000_000;
106        let cases = [
107            CacheUse::sent(use_at),
108            CacheUse::sent(use_at).renewed(),
109            CacheUse::sent(0),
110        ];
111        for cache in cases {
112            assert_eq!(renewal_due(KeepWarm::default(), cache, LEAD_MS), None);
113        }
114    }
115
116    #[test]
117    fn five_minute_renews_a_used_prefix_once_just_before_it_expires() {
118        let use_at = 1_000_000;
119        let first = renewal_due(KeepWarm::FiveMinute, CacheUse::sent(use_at), LEAD_MS);
120        assert_eq!(first, Some(use_at + TTL_MS - MARGIN_MS - LEAD_MS));
121        let renewed = CacheUse::sent(use_at).renewed();
122        assert_eq!(renewal_due(KeepWarm::FiveMinute, renewed, LEAD_MS), None);
123    }
124
125    /// What a door meets, as `Event` in `crates/kernel/spec/KeepWarm.lean`:
126    /// a real request carrying the prefix sent at an instant, or a wake at
127    /// an instant with the lead its endpoint reads then.
128    #[derive(Debug, Clone, Copy)]
129    enum Event {
130        Used { at_ms: u64 },
131        Woke { now_ms: u64, lead_ms: u64 },
132    }
133
134    /// Instants within a few lifetimes of each other, and leads from none
135    /// to longer than a lifetime, so a wake can fall before, at or after
136    /// any renewal's due instant.
137    fn event() -> impl Strategy<Value = Event> {
138        prop_oneof![
139            (0..4 * TTL_MS).prop_map(|at_ms| Event::Used { at_ms }),
140            (0..4 * TTL_MS, 0..2 * TTL_MS)
141                .prop_map(|(now_ms, lead_ms)| Event::Woke { now_ms, lead_ms }),
142        ]
143    }
144
145    fn setting() -> impl Strategy<Value = KeepWarm> {
146        prop_oneof![Just(KeepWarm::Off), Just(KeepWarm::FiveMinute)]
147    }
148
149    proptest! {
150        /// `at_most_one_renewal_per_use`, `every_renewal_leaves_the_margin`
151        /// and `off_sends_nothing` of `crates/kernel/spec/KeepWarm.lean`,
152        /// on any trace of uses and wakes with any lead at each wake. The
153        /// door is driven here as `Door.step` drives it, and the renewals
154        /// are counted from the ones this driver sends.
155        #[test]
156        fn every_trace_keeps_the_lean_properties(
157            setting in setting(),
158            events in proptest::collection::vec(event(), 0..40),
159        ) {
160            let mut cache = None;
161            let mut since_use = 0_u32;
162            for event in events {
163                match event {
164                    Event::Used { at_ms } => {
165                        cache = Some((at_ms, CacheUse::sent(at_ms)));
166                        since_use = 0;
167                    }
168                    Event::Woke { now_ms, lead_ms } => {
169                        let Some((used_ms, kept)) = cache else { continue };
170                        let Some(due) = renewal_due(setting, kept, lead_ms) else { continue };
171                        if due > now_ms {
172                            continue;
173                        }
174                        prop_assert_eq!(setting, KeepWarm::FiveMinute, "Off sent a renewal");
175                        prop_assert!(
176                            due <= (used_ms + TTL_MS).saturating_sub(MARGIN_MS),
177                            "a renewal due at {due} for a use at {used_ms} leaves no margin"
178                        );
179                        since_use += 1;
180                        prop_assert!(since_use <= 1, "a second renewal for the use at {used_ms}");
181                        cache = Some((used_ms, kept.renewed()));
182                    }
183                }
184            }
185        }
186    }
187}