1 #[cfg(test)] 2 mod periodic_timer_test; 3 4 use tokio::sync::{mpsc, Mutex}; 5 use tokio::time::Duration; 6 7 use std::sync::Arc; 8 9 use async_trait::async_trait; 10 11 #[derive(Default, Debug, Copy, Clone, PartialEq, Eq)] 12 pub enum TimerIdRefresh { 13 #[default] 14 Alloc, 15 Perms, 16 } 17 18 // PeriodicTimerTimeoutHandler is a handler called on timeout 19 #[async_trait] 20 pub trait PeriodicTimerTimeoutHandler { on_timeout(&mut self, id: TimerIdRefresh)21 async fn on_timeout(&mut self, id: TimerIdRefresh); 22 } 23 24 // PeriodicTimer is a periodic timer 25 #[derive(Default)] 26 pub struct PeriodicTimer { 27 id: TimerIdRefresh, 28 interval: Duration, 29 close_tx: Mutex<Option<mpsc::Sender<()>>>, 30 } 31 32 impl PeriodicTimer { 33 // create a new timer new(id: TimerIdRefresh, interval: Duration) -> Self34 pub fn new(id: TimerIdRefresh, interval: Duration) -> Self { 35 PeriodicTimer { 36 id, 37 interval, 38 close_tx: Mutex::new(None), 39 } 40 } 41 42 // Start starts the timer. start<T: 'static + PeriodicTimerTimeoutHandler + std::marker::Send>( &self, timeout_handler: Arc<Mutex<T>>, ) -> bool43 pub async fn start<T: 'static + PeriodicTimerTimeoutHandler + std::marker::Send>( 44 &self, 45 timeout_handler: Arc<Mutex<T>>, 46 ) -> bool { 47 // this is a noop if the timer is always running 48 { 49 let close_tx = self.close_tx.lock().await; 50 if close_tx.is_some() { 51 return false; 52 } 53 } 54 55 let (close_tx, mut close_rx) = mpsc::channel(1); 56 let interval = self.interval; 57 let id = self.id; 58 59 tokio::spawn(async move { 60 loop { 61 let timer = tokio::time::sleep(interval); 62 tokio::pin!(timer); 63 64 tokio::select! { 65 _ = timer.as_mut() => { 66 let mut handler = timeout_handler.lock().await; 67 handler.on_timeout(id).await; 68 } 69 _ = close_rx.recv() => break, 70 } 71 } 72 }); 73 74 { 75 let mut close = self.close_tx.lock().await; 76 *close = Some(close_tx); 77 } 78 79 true 80 } 81 82 // Stop stops the timer. stop(&self)83 pub async fn stop(&self) { 84 let mut close_tx = self.close_tx.lock().await; 85 close_tx.take(); 86 } 87 88 // is_running tests if the timer is running. 89 // Debug purpose only is_running(&self) -> bool90 pub async fn is_running(&self) -> bool { 91 let close_tx = self.close_tx.lock().await; 92 close_tx.is_some() 93 } 94 } 95