Skip to content
67 linesCodeBlameRaw
1//! g1t-wide pauses (billing's `platform_pause`; docs/SPEND-GUARDRAILS.md),
2//! read cheaply: one call to billing per isolate every 30 seconds at most,
3//! never a database read per request.
4//!
5//! When billing cannot say, nothing is paused: a pause is a brake staff
6//! (or billing's usage watcher) pull on purpose, and a billing outage must
7//! not stop schedules and indexing everywhere. The failure is kept for the
8//! same 30 seconds, so an outage is not asked about on every request.
9
10use std::cell::RefCell;
11
12use g1t_contracts::billing::{PauseLevel, PlatformPause};
13use worker::Fetcher;
14
15/// How long an answer is kept in the isolate.
16pub const KEEP_MS: u64 = 30_000;
17
18thread_local! {
19 static KEPT: RefCell<Option<(PlatformPause, u64)>> = const { RefCell::new(None) };
20}
21
22/// A kept answer, while it is fresh.
23fn fresh(kept: Option<(PlatformPause, u64)>, now: u64) -> Option<PlatformPause> {
24 kept.filter(|(_, until)| *until > now).map(|(pause, _)| pause)
25}
26
27/// Whether `level` is paused, through the billing service's binding.
28pub async fn paused(billing: &Fetcher, level: PauseLevel) -> bool {
29 current(billing).await.is(level)
30}
31
32/// Every level, kept for `KEEP_MS`. Nothing paused when billing cannot say.
33pub async fn current(billing: &Fetcher) -> PlatformPause {
34 let now = crate::now_ms();
35 if let Some(pause) = KEPT.with(|kept| fresh(*kept.borrow(), now)) {
36 return pause;
37 }
38 let pause = match crate::call::<_, PlatformPause>(billing, "platform_pause", &serde_json::json!({})).await {
39 Ok(pause) => pause,
40 Err(error) => {
41 worker::console_error!("platform pause unreadable, so nothing is paused: {error}");
42 PlatformPause::default()
43 }
44 };
45 KEPT.with(|kept| *kept.borrow_mut() = Some((pause, now + KEEP_MS)));
46 pause
47}
48
49#[cfg(test)]
50mod tests {
51 use super::*;
52
53 #[test]
54 fn an_answer_is_kept_thirty_seconds() {
55 let paused = PlatformPause { schedules: true, ..PlatformPause::default() };
56 assert_eq!(fresh(Some((paused, 1_000 + KEEP_MS)), 1_000), Some(paused));
57 assert_eq!(fresh(Some((paused, 1_000 + KEEP_MS)), 1_000 + KEEP_MS), None);
58 assert_eq!(fresh(None, 0), None);
59 }
60
61 #[test]
62 fn an_unread_pause_pauses_nothing() {
63 let none = PlatformPause::default();
64 assert!(PauseLevel::ALL.into_iter().all(|level| !none.is(level)));
65 assert!(!none.any());
66 }
67}