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}