973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 1 | //! Idle-exit tracking for the socket-activated static-only server: counts |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 2 | //! in-flight requests and the time of the last one, so the process can shut |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 3 | //! itself down once nothing has happened for `MALICE_IDLE_EXIT_SECS`. |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 4 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 5 | use std::{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 6 | sync::{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 7 | Arc, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 8 | atomic::{AtomicUsize, Ordering}, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 9 | }, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 10 | time::{Duration, Instant}, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 11 | }; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 12 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 13 | use axum::{extract::State, middleware::Next}; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 14 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 15 | #[derive(Clone)] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 16 | pub struct IdleTracker { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 17 | in_flight: Arc<AtomicUsize>, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 18 | last_activity: Arc<std::sync::Mutex<Instant>>, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 19 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 20 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 21 | impl IdleTracker { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 22 | pub fn new() -> Self { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 23 | Self { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 24 | in_flight: Arc::new(AtomicUsize::new(0)), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 25 | last_activity: Arc::new(std::sync::Mutex::new(Instant::now())), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 26 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 27 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 28 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 29 | fn touch(&self) { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 30 | *self |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 31 | .last_activity |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 32 | .lock() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 33 | .expect("idle tracker mutex poisoned") = Instant::now(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 34 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 35 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 36 | /// Resolves once no request has started or finished, and none is still |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 37 | /// in flight, for at least `idle`. |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 38 | pub async fn wait_idle(&self, idle: Duration) { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 39 | loop { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 40 | tokio::time::sleep(idle.min(Duration::from_millis(200))).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 41 | if self.in_flight.load(Ordering::SeqCst) != 0 { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 42 | continue; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 43 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 44 | let elapsed = self |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 45 | .last_activity |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 46 | .lock() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 47 | .expect("idle tracker mutex poisoned") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 48 | .elapsed(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 49 | if elapsed >= idle { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 50 | return; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 51 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 52 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 53 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 54 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 55 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 56 | impl Default for IdleTracker { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 57 | fn default() -> Self { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 58 | Self::new() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 59 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 60 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 61 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 62 | pub async fn track( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 63 | State(idle): State<IdleTracker>, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 64 | request: axum::extract::Request, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 65 | next: Next, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 66 | ) -> axum::response::Response { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 67 | idle.in_flight.fetch_add(1, Ordering::SeqCst); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 68 | idle.touch(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 69 | let response = next.run(request).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 70 | idle.touch(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 71 | idle.in_flight.fetch_sub(1, Ordering::SeqCst); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 72 | response |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 73 | } |