Skip to main content

forge_browser/
idle.rs

1//! The idle release: a profile nobody has touched for a while closes.
2//!
3//! **The clock is the server's**, because the browser and the profiles are:
4//! every call and every attach touches the profile, and a sweep reads the
5//! clocks. A close ends the host and its tabs, not the profile: the next call
6//! starts a fresh browser with the logins intact.
7//!
8//! **A held tab holds the profile open.** A parked hand-off is a person being
9//! asked to act in the page, so the sweep skips a profile with any held tab
10//! rather than closing it under them; the hold's resolution is what lets the
11//! clock close it later.
12
13use std::time::{Duration, Instant};
14
15/// How often the sweep looks. The clock is coarse on purpose: a browser
16/// sitting a few seconds past its bound is not a fault.
17pub const SWEEP: Duration = Duration::from_secs(30);
18
19/// One profile's clock.
20#[derive(Clone, Copy, Debug)]
21pub struct Touch {
22    /// When a seat last used the profile: a call and an attach.
23    pub at: Instant,
24}
25
26impl Touch {
27    pub fn now() -> Self {
28        Self { at: Instant::now() }
29    }
30
31    pub fn touched(&mut self) {
32        *self = Self::now();
33    }
34}
35
36/// What one candidate's clock says this pass.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub enum Step {
39    Nothing,
40    Close,
41}
42
43/// The clock itself, pure: what the sweep should do for a profile touched at
44/// `last`, now.
45pub fn step(last: Instant, now: Instant, idle: Duration) -> Step {
46    if now.duration_since(last) < idle { Step::Nothing } else { Step::Close }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    const IDLE: Duration = Duration::from_secs(30 * 60);
54
55    /// The clock, one case per arm: a fresh touch sits quiet, and a use
56    /// restarts the bound.
57    #[test]
58    fn the_clock_closes_at_the_bound_and_a_touch_resets_it() {
59        let base = Instant::now();
60        assert_eq!(
61            step(base, base + Duration::from_secs(60), IDLE),
62            Step::Nothing,
63            "a fresh touch is quiet",
64        );
65        let inside = (base + IDLE).checked_sub(Duration::from_secs(1)).expect("the test's clock");
66        assert_eq!(
67            step(base, inside, IDLE),
68            Step::Nothing,
69            "just inside the bound it is still quiet"
70        );
71        assert_eq!(step(base, base + IDLE, IDLE), Step::Close, "at the bound it closes");
72        assert_eq!(
73            step(base + Duration::from_secs(60), base + IDLE, IDLE),
74            Step::Nothing,
75            "a touch inside the old bound makes the browser fresh again",
76        );
77    }
78}