Skip to main content

synd_api/session/
idle_shutdown.rs

1use std::time::Duration;
2
3use tokio_util::sync::CancellationToken;
4use tracing::info;
5
6use crate::shutdown::{Shutdown, ShutdownReason};
7
8/// Idle shutdown policy for daemon sessions.
9#[derive(Clone)]
10pub struct SessionIdleShutdown {
11    grace: Duration,
12    shutdown: Shutdown,
13}
14
15impl SessionIdleShutdown {
16    pub fn new(grace: Duration, shutdown: Shutdown) -> Self {
17        Self { grace, shutdown }
18    }
19
20    pub fn grace(&self) -> Duration {
21        self.grace
22    }
23
24    fn schedule(&self, timer: IdleShutdownTimer) {
25        let grace = self.grace;
26        let shutdown = self.shutdown.clone();
27
28        tokio::spawn(async move {
29            tokio::select! {
30                () = timer.cancelled() => {}
31                () = tokio::time::sleep(grace) => {
32                    info!(
33                        idle_shutdown_grace_ms = grace.as_millis(),
34                        "Daemon session idle grace elapsed"
35                    );
36                    shutdown.shutdown_with_reason(ShutdownReason::Idle);
37                }
38            }
39        });
40    }
41}
42
43impl std::fmt::Debug for SessionIdleShutdown {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        f.debug_struct("SessionIdleShutdown")
46            .field("grace", &self.grace)
47            .finish_non_exhaustive()
48    }
49}
50
51/// Cancellable timer token for one pending idle shutdown.
52#[derive(Clone)]
53pub(super) struct IdleShutdownTimer {
54    cancellation: CancellationToken,
55}
56
57impl IdleShutdownTimer {
58    pub(super) fn new() -> Self {
59        Self {
60            cancellation: CancellationToken::new(),
61        }
62    }
63
64    fn cancel(self) {
65        self.cancellation.cancel();
66    }
67
68    async fn cancelled(&self) {
69        self.cancellation.cancelled().await;
70    }
71}
72
73impl std::fmt::Debug for IdleShutdownTimer {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.debug_struct("IdleShutdownTimer").finish_non_exhaustive()
76    }
77}
78
79/// Side effect selected after daemon session state changes.
80#[derive(Debug, Clone)]
81pub(super) enum DaemonSessionsEffect {
82    None,
83    CancelIdleShutdown { timer: IdleShutdownTimer },
84    ScheduleIdleShutdown { timer: IdleShutdownTimer },
85}
86
87impl PartialEq for DaemonSessionsEffect {
88    fn eq(&self, other: &Self) -> bool {
89        std::mem::discriminant(self) == std::mem::discriminant(other)
90    }
91}
92
93impl Eq for DaemonSessionsEffect {}
94
95impl DaemonSessionsEffect {
96    pub(super) fn apply(self, idle_shutdown: Option<&SessionIdleShutdown>) {
97        match self {
98            Self::None => {}
99            Self::CancelIdleShutdown { timer } => timer.cancel(),
100            Self::ScheduleIdleShutdown { timer } => {
101                if let Some(idle_shutdown) = idle_shutdown {
102                    idle_shutdown.schedule(timer);
103                }
104            }
105        }
106    }
107}