canic_core/ops/ic/
timer.rs1pub use crate::cdk::timers::TimerId;
2
3use crate::{
4 cdk::timers::{
5 clear_timer as cdk_clear_timer, set_timer as cdk_set_timer,
6 set_timer_interval as cdk_set_timer_interval,
7 },
8 model::metrics::{
9 system::{SystemMetricKind, SystemMetrics},
10 timer::{TimerMetrics, TimerMode},
11 },
12 ops::perf::PerfOps,
13 perf::perf_counter,
14};
15use std::{cell::RefCell, future::Future, rc::Rc, thread::LocalKey, time::Duration};
16
17pub struct TimerOps;
22
23impl TimerOps {
24 pub fn set(
27 delay: Duration,
28 label: impl Into<String>,
29 task: impl Future<Output = ()> + 'static,
30 ) -> TimerId {
31 let label = label.into();
32
33 SystemMetrics::increment(SystemMetricKind::TimerScheduled);
34 TimerMetrics::ensure(TimerMode::Once, delay, label.as_str());
35
36 cdk_set_timer(delay, async move {
37 TimerMetrics::increment(TimerMode::Once, delay, label.as_str());
38
39 let start = perf_counter();
40 task.await;
41 let end = perf_counter();
42
43 PerfOps::record(label.as_str(), end.saturating_sub(start));
44 })
45 }
46
47 pub fn set_interval<F, Fut>(interval: Duration, label: impl Into<String>, task: F) -> TimerId
50 where
51 F: FnMut() -> Fut + 'static,
52 Fut: Future<Output = ()> + 'static,
53 {
54 let label = Rc::new(label.into());
56
57 SystemMetrics::increment(SystemMetricKind::TimerScheduled);
58 TimerMetrics::ensure(TimerMode::Interval, interval, label.as_str());
59
60 let task = Rc::new(RefCell::new(task));
61
62 cdk_set_timer_interval(interval, move || {
63 let label = Rc::clone(&label);
64 let task = Rc::clone(&task);
65
66 async move {
67 TimerMetrics::increment(TimerMode::Interval, interval, label.as_str());
68
69 let start = perf_counter();
70 let fut = { (task.borrow_mut())() };
71 fut.await;
72 let end = perf_counter();
73
74 PerfOps::record(label.as_str(), end.saturating_sub(start));
75 }
76 })
77 }
78
79 pub fn clear(id: TimerId) {
81 cdk_clear_timer(id);
82 }
83
84 pub fn set_guarded(
87 slot: &'static LocalKey<RefCell<Option<TimerId>>>,
88 delay: Duration,
89 label: impl Into<String>,
90 task: impl Future<Output = ()> + 'static,
91 ) -> bool {
92 slot.with_borrow_mut(|entry| {
93 if entry.is_some() {
94 return false;
95 }
96
97 let id = Self::set(delay, label, task);
98 *entry = Some(id);
99 true
100 })
101 }
102
103 pub fn set_guarded_interval<FInit, InitFut, FTick, TickFut>(
107 slot: &'static LocalKey<RefCell<Option<TimerId>>>,
108 init_delay: Duration,
109 init_label: impl Into<String>,
110 init_task: FInit,
111 interval: Duration,
112 interval_label: impl Into<String>,
113 tick_task: FTick,
114 ) -> bool
115 where
116 FInit: FnOnce() -> InitFut + 'static,
117 InitFut: Future<Output = ()> + 'static,
118 FTick: FnMut() -> TickFut + 'static,
119 TickFut: Future<Output = ()> + 'static,
120 {
121 let init_label = init_label.into();
122 let interval_label = interval_label.into();
123
124 slot.with_borrow_mut(|entry| {
125 if entry.is_some() {
126 return false;
127 }
128
129 let init_id_cell = Rc::new(RefCell::new(None));
130 let init_id_cell_task = Rc::clone(&init_id_cell);
131
132 let init_id = Self::set(init_delay, init_label, async move {
133 init_task().await;
134
135 let init_id = init_id_cell_task.borrow();
136 let Some(init_id) = init_id.as_ref() else {
137 return;
138 };
139
140 let still_armed = slot.with_borrow(|slot_val| slot_val.as_ref() == Some(init_id));
141 if !still_armed {
142 return;
143 }
144
145 let interval_id = Self::set_interval(interval, interval_label, tick_task);
146
147 slot.with_borrow_mut(|slot_val| {
150 let old = slot_val.replace(interval_id);
151 if let Some(old_id) = old
152 && old_id != interval_id
153 {
154 Self::clear(old_id);
155 }
156 });
157 });
158
159 *init_id_cell.borrow_mut() = Some(init_id);
160 *entry = Some(init_id);
161 true
162 })
163 }
164
165 #[must_use]
168 pub fn clear_guarded(slot: &'static LocalKey<RefCell<Option<TimerId>>>) -> bool {
169 slot.with_borrow_mut(|entry| {
170 entry.take().is_some_and(|id| {
171 Self::clear(id);
172 true
173 })
174 })
175 }
176}