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