use core::fmt;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt::Debug;
use std::hash::Hash;
use std::ops::RangeFrom;
use std::sync::Arc;
use std::time::Duration;
use tokio::task::JoinHandle;
use tracing::trace;
use super::output_port::{OutputPort, OutputPortSubscriber};
#[derive(Debug)]
struct Timer<Key> {
key: Key,
task: JoinHandle<()>,
generation: u64,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct TimeoutElapsed<Key> {
key: Key,
generation: u64,
}
impl<Key> TimeoutElapsed<Key> {
pub fn display_key(&self) -> &dyn fmt::Display
where
Key: fmt::Display,
{
&self.key
}
}
pub struct TimerScheduler<Key>
where
Key: Clone + Eq + Hash + Send + 'static,
{
output_port: Arc<OutputPort<TimeoutElapsed<Key>>>,
timers: HashMap<Key, Timer<Key>>,
generations: RangeFrom<u64>,
}
impl<Key> TimerScheduler<Key>
where
Key: Clone + Eq + Hash + Send + 'static,
{
pub fn new(subscriber: OutputPortSubscriber<TimeoutElapsed<Key>>) -> Self {
let output_port = OutputPort::with_capacity(32);
subscriber.subscribe_to_port(&output_port);
Self {
output_port: Arc::new(output_port),
timers: HashMap::new(),
generations: 1..,
}
}
pub fn start_timer(&mut self, key: Key, timeout: Duration)
where
Key: Clone + Send + 'static,
{
self.cancel(&key);
let generation = self
.generations
.next()
.expect("generation counter overflowed");
let task = {
let key = key.clone();
let output_port = Arc::clone(&self.output_port);
tokio::spawn(async move {
tokio::time::sleep(timeout).await;
output_port.send(TimeoutElapsed { key, generation })
})
};
self.timers.insert(
key.clone(),
Timer {
key,
task,
generation,
},
);
}
pub fn is_timer_active(&self, key: &Key) -> bool {
self.timers.contains_key(key)
}
pub fn cancel(&mut self, key: &Key) {
if let Some(timer) = self.timers.remove(key) {
timer.task.abort();
}
}
pub fn cancel_all(&mut self) {
self.timers.drain().for_each(|(_, timer)| {
timer.task.abort();
});
}
pub fn intercept_timer_msg(&mut self, timer_msg: TimeoutElapsed<Key>) -> Option<Key>
where
Key: Debug,
{
match self.timers.entry(timer_msg.key) {
Entry::Vacant(entry) => {
let key = entry.key();
trace!("Received timer {key:?} that has been removed, discarding");
None
}
Entry::Occupied(entry) if timer_msg.generation != entry.get().generation => {
let (key, timer) = (entry.key(), entry.get());
trace!(
"Received timer {key:?} from old generation {}, expected generation {}, discarding",
timer_msg.generation,
timer.generation,
);
None
}
Entry::Occupied(entry) => {
let timer = entry.remove();
Some(timer.key)
}
}
}
}
impl<Key> Drop for TimerScheduler<Key>
where
Key: Clone + Eq + Hash + Send + 'static,
{
fn drop(&mut self) {
self.cancel_all();
}
}
#[cfg(test)]
mod tests {
use super::*;
use ractor::{Actor, ActorRef};
use std::time::Duration;
use tokio::time::sleep;
#[derive(Copy, Debug, Clone, PartialEq, Eq, Hash)]
struct TestKey(&'static str);
#[derive(Debug)]
struct TestMsg(TimeoutElapsed<TestKey>);
impl From<TimeoutElapsed<TestKey>> for TestMsg {
fn from(timer_msg: TimeoutElapsed<TestKey>) -> Self {
TestMsg(timer_msg)
}
}
struct TestActor;
#[async_trait::async_trait]
impl Actor for TestActor {
type State = ();
type Arguments = ();
type Msg = TestMsg;
async fn pre_start(
&self,
_myself: ActorRef<TestMsg>,
_args: (),
) -> Result<(), ractor::ActorProcessingErr> {
Ok(())
}
async fn handle(
&self,
_myself: ActorRef<TestMsg>,
TestMsg(elapsed): TestMsg,
_state: &mut (),
) -> Result<(), ractor::ActorProcessingErr> {
println!("Received timer message: {elapsed:?}");
Ok(())
}
}
async fn spawn() -> TimerScheduler<TestKey> {
let actor_ref = TestActor::spawn(None, TestActor, ()).await.unwrap().0;
TimerScheduler::new(Box::new(actor_ref))
}
#[tokio::test]
async fn test_start_timer() {
let mut scheduler = spawn().await;
let key = TestKey("timer1");
scheduler.start_timer(key, Duration::from_millis(100));
assert!(scheduler.is_timer_active(&key));
sleep(Duration::from_millis(150)).await;
let elapsed_key = scheduler.intercept_timer_msg(TimeoutElapsed { key, generation: 1 });
assert_eq!(elapsed_key, Some(key));
assert!(!scheduler.is_timer_active(&key));
}
#[tokio::test]
async fn test_cancel_timer() {
let mut scheduler = spawn().await;
let key = TestKey("timer1");
scheduler.start_timer(key, Duration::from_millis(100));
scheduler.cancel(&key);
assert!(!scheduler.is_timer_active(&key));
}
#[tokio::test]
async fn test_cancel_all_timers() {
let mut scheduler = spawn().await;
scheduler.start_timer(TestKey("timer1"), Duration::from_millis(100));
scheduler.start_timer(TestKey("timer2"), Duration::from_millis(200));
scheduler.cancel_all();
assert!(!scheduler.is_timer_active(&TestKey("timer1")));
assert!(!scheduler.is_timer_active(&TestKey("timer2")));
}
#[tokio::test]
async fn test_intercept_timer_msg_valid() {
let mut scheduler = spawn().await;
let key = TestKey("timer1");
scheduler.start_timer(key, Duration::from_millis(100));
sleep(Duration::from_millis(150)).await;
let timer_msg = TimeoutElapsed { key, generation: 1 };
let intercepted_msg = scheduler.intercept_timer_msg(timer_msg);
assert_eq!(intercepted_msg, Some(key));
}
#[tokio::test]
async fn test_intercept_timer_msg_invalid_generation() {
let mut scheduler = spawn().await;
let key = TestKey("timer1");
scheduler.start_timer(key, Duration::from_millis(100));
scheduler.start_timer(key, Duration::from_millis(200));
let timer_msg = TimeoutElapsed { key, generation: 1 };
let intercepted_msg = scheduler.intercept_timer_msg(timer_msg);
assert_eq!(intercepted_msg, None);
}
#[tokio::test]
async fn test_intercept_timer_msg_cancelled() {
let mut scheduler = spawn().await;
let key = TestKey("timer1");
scheduler.start_timer(key, Duration::from_millis(100));
scheduler.cancel(&key);
let timer_msg = TimeoutElapsed { key, generation: 1 };
let intercepted_msg = scheduler.intercept_timer_msg(timer_msg);
assert_eq!(intercepted_msg, None);
}
}