Skip to main content

links_core/scheduler/
task.rs

1use chrono::{DateTime, Utc};
2use std::{
3    cmp::Reverse,
4    fmt::{Debug, Display},
5    time::Duration,
6};
7
8use crate::asserted_short_name;
9
10/// Enum representing the status of a timer task
11#[derive(Debug, PartialEq)]
12pub enum TimerTaskStatus {
13    Completed,
14    Terminate,
15    RetryAfter(Duration),
16}
17
18pub type Executable = Box<dyn FnMut() -> TimerTaskStatus + Send + 'static>;
19
20pub struct Task {
21    name: String,
22    execute_at: DateTime<Utc>,
23    interval: Duration,
24    executable: Executable,
25}
26impl Task {
27    pub fn new(name: &str, interval: Duration, task: Executable) -> Self {
28        Task {
29            name: name.to_owned(),
30            execute_at: chrono::Utc::now(),
31            interval,
32            executable: task,
33        }
34    }
35    /// Execute the task
36    pub fn execute(&mut self) -> TimerTaskStatus {
37        (self.executable)()
38    }
39    /// Reschedule the task to be executed at the next standard interval
40    pub fn reschedule(&mut self) {
41        self.execute_at += self.interval;
42    }
43    /// Reschedule the task to be executed at the next custom interval
44    pub fn reschedule_with_interval(&mut self, interval: Duration) {
45        self.execute_at += interval;
46    }
47    /// Get [DateTime] of the next scheduled execution
48    pub fn execute_at(&self) -> &DateTime<Utc> {
49        &self.execute_at
50    }
51}
52impl Display for Task {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.debug_struct(asserted_short_name!("Task", Self))
55            .field("name", &self.name)
56            .field("execute_at", &self.execute_at)
57            .field("interval", &self.interval)
58            .finish()
59    }
60}
61impl Debug for Task {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.debug_struct(asserted_short_name!("Task", Self))
64            .field("name", &self.name)
65            .field("execute_at", &self.execute_at)
66            .field("interval", &self.interval)
67            .field("executable", &"dyn FnMut() -> TaskStatus")
68            .finish()
69    }
70}
71impl Ord for Task {
72    #[inline]
73    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
74        Reverse(self.execute_at).cmp(&Reverse(other.execute_at))
75    }
76}
77impl PartialOrd for Task {
78    #[inline]
79    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
80        Some(Reverse(self.execute_at).cmp(&Reverse(other.execute_at)))
81    }
82}
83impl PartialEq for Task {
84    #[inline]
85    fn eq(&self, other: &Self) -> bool {
86        self.execute_at == other.execute_at
87    }
88}
89impl Eq for Task {}
90
91#[cfg(test)]
92mod test {
93    use super::*;
94    use crate::unittest::setup;
95    use log::info;
96    use std::collections::BinaryHeap;
97
98    #[test]
99    fn test_task_schedule() {
100        setup::log::configure_compact(log::LevelFilter::Info);
101
102        // ensure the BinaryHeap implements a min-heap, meaning a TaskSchedule with the earlier date will be popped first
103
104        let mut schedules = BinaryHeap::new();
105        let interval = Duration::from_secs(1);
106        schedules.push(Task::new("Task1", interval, Box::new(|| TimerTaskStatus::Completed)));
107        schedules.push(Task::new("Task2", interval, Box::new(|| TimerTaskStatus::Completed)));
108        schedules.push(Task::new("Task3", interval, Box::new(|| TimerTaskStatus::Completed)));
109
110        let task = schedules.pop().unwrap();
111        info!("task: {}", task);
112        assert_eq!(task.name, "Task1");
113
114        let task = schedules.pop().unwrap();
115        info!("task: {}", task);
116        assert_eq!(task.name, "Task2");
117
118        let mut task = schedules.pop().unwrap();
119        info!("task: {}", task);
120        assert_eq!(task.name, "Task3");
121
122        // reschedule the task
123        let last_execution = task.execute_at;
124        task.reschedule();
125        info!("task: {:?}", task);
126        assert_eq!(task.execute_at, last_execution + interval);
127    }
128}