1use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::Duration;
9
10use chrono::{DateTime, Utc};
11use dashmap::DashMap;
12use serde::{Deserialize, Serialize};
13use tokio_util::sync::CancellationToken;
14
15use crate::ipc::{KernelMessage, MessagePayload};
16use crate::process::Pid;
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct TimerInfo {
21 pub id: String,
23 pub owner_pid: Pid,
25 pub fire_at: DateTime<Utc>,
27 pub repeat_interval_ms: Option<u64>,
29 pub cancelled: bool,
31}
32
33pub struct TimerEntry {
35 pub id: String,
37 pub owner_pid: Pid,
39 pub fire_at: DateTime<Utc>,
41 pub repeat_interval: Option<Duration>,
43 pub payload: MessagePayload,
45 pub cancel_token: CancellationToken,
47}
48
49impl TimerEntry {
50 pub fn info(&self) -> TimerInfo {
52 TimerInfo {
53 id: self.id.clone(),
54 owner_pid: self.owner_pid,
55 fire_at: self.fire_at,
56 repeat_interval_ms: self.repeat_interval.map(|d| d.as_millis() as u64),
57 cancelled: self.cancel_token.is_cancelled(),
58 }
59 }
60}
61
62pub struct TimerService {
68 timers: DashMap<String, TimerEntry>,
69 next_id: AtomicU64,
70}
71
72impl TimerService {
73 pub fn new() -> Self {
75 Self {
76 timers: DashMap::new(),
77 next_id: AtomicU64::new(1),
78 }
79 }
80
81 fn next_timer_id(&self) -> String {
83 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
84 format!("timer-{id}")
85 }
86
87 pub fn create_oneshot(
92 &self,
93 owner_pid: Pid,
94 delay: Duration,
95 payload: MessagePayload,
96 ) -> (String, CancellationToken) {
97 let id = self.next_timer_id();
98 let cancel_token = CancellationToken::new();
99 let fire_at = Utc::now() + chrono::Duration::from_std(delay).unwrap_or_default();
100
101 let entry = TimerEntry {
102 id: id.clone(),
103 owner_pid,
104 fire_at,
105 repeat_interval: None,
106 payload,
107 cancel_token: cancel_token.clone(),
108 };
109
110 self.timers.insert(id.clone(), entry);
111 (id, cancel_token)
112 }
113
114 pub fn create_repeating(
119 &self,
120 owner_pid: Pid,
121 interval: Duration,
122 payload: MessagePayload,
123 ) -> (String, CancellationToken) {
124 let id = self.next_timer_id();
125 let cancel_token = CancellationToken::new();
126 let fire_at = Utc::now() + chrono::Duration::from_std(interval).unwrap_or_default();
127
128 let entry = TimerEntry {
129 id: id.clone(),
130 owner_pid,
131 fire_at,
132 repeat_interval: Some(interval),
133 payload,
134 cancel_token: cancel_token.clone(),
135 };
136
137 self.timers.insert(id.clone(), entry);
138 (id, cancel_token)
139 }
140
141 pub fn cancel(&self, timer_id: &str) -> bool {
145 if let Some(entry) = self.timers.get(timer_id) {
146 entry.cancel_token.cancel();
147 true
148 } else {
149 false
150 }
151 }
152
153 pub fn cancel_for_pid(&self, pid: Pid) -> usize {
158 let mut cancelled = 0;
159 for entry in self.timers.iter() {
160 if entry.owner_pid == pid {
161 entry.cancel_token.cancel();
162 cancelled += 1;
163 }
164 }
165 cancelled
166 }
167
168 pub fn remove(&self, timer_id: &str) -> Option<TimerEntry> {
170 self.timers.remove(timer_id).map(|(_, e)| e)
171 }
172
173 pub fn cleanup_cancelled(&self) -> usize {
175 let cancelled: Vec<String> = self
176 .timers
177 .iter()
178 .filter(|e| e.cancel_token.is_cancelled())
179 .map(|e| e.key().clone())
180 .collect();
181 let count = cancelled.len();
182 for id in cancelled {
183 self.timers.remove(&id);
184 }
185 count
186 }
187
188 pub fn info(&self, timer_id: &str) -> Option<TimerInfo> {
190 self.timers.get(timer_id).map(|e| e.info())
191 }
192
193 pub fn list(&self) -> Vec<String> {
195 self.timers.iter().map(|e| e.key().clone()).collect()
196 }
197
198 pub fn list_info(&self) -> Vec<TimerInfo> {
200 self.timers.iter().map(|e| e.value().info()).collect()
201 }
202
203 pub fn list_for_pid(&self, pid: Pid) -> Vec<TimerInfo> {
205 self.timers
206 .iter()
207 .filter(|e| e.owner_pid == pid)
208 .map(|e| e.info())
209 .collect()
210 }
211
212 pub fn len(&self) -> usize {
214 self.timers.len()
215 }
216
217 pub fn is_empty(&self) -> bool {
219 self.timers.is_empty()
220 }
221
222 pub fn collect_due(&self) -> Vec<(Pid, KernelMessage)> {
228 let now = Utc::now();
229 let mut due = Vec::new();
230 let mut to_remove = Vec::new();
231 let mut to_reschedule = Vec::new();
232
233 for entry in self.timers.iter() {
234 if entry.cancel_token.is_cancelled() {
235 to_remove.push(entry.key().clone());
236 continue;
237 }
238
239 if entry.fire_at <= now {
240 let msg = KernelMessage::new(
241 0, crate::ipc::MessageTarget::Process(entry.owner_pid),
243 entry.payload.clone(),
244 );
245 due.push((entry.owner_pid, msg));
246
247 if let Some(interval) = entry.repeat_interval {
248 to_reschedule.push((entry.key().clone(), interval));
249 } else {
250 to_remove.push(entry.key().clone());
251 }
252 }
253 }
254
255 for id in &to_remove {
257 self.timers.remove(id);
258 }
259
260 for (id, interval) in to_reschedule {
262 if let Some(mut entry) = self.timers.get_mut(&id) {
263 entry.fire_at =
264 Utc::now() + chrono::Duration::from_std(interval).unwrap_or_default();
265 }
266 }
267
268 due
269 }
270}
271
272impl Default for TimerService {
273 fn default() -> Self {
274 Self::new()
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use super::*;
281 use crate::ipc::MessagePayload;
282
283 fn text_payload(msg: &str) -> MessagePayload {
284 MessagePayload::Text(msg.into())
285 }
286
287 #[test]
288 fn create_oneshot_timer() {
289 let service = TimerService::new();
290 let (id, _token) =
291 service.create_oneshot(1, Duration::from_secs(10), text_payload("fire!"));
292
293 assert!(service.info(&id).is_some());
294 assert_eq!(service.len(), 1);
295 let info = service.info(&id).unwrap();
296 assert_eq!(info.owner_pid, 1);
297 assert!(info.repeat_interval_ms.is_none());
298 assert!(!info.cancelled);
299 }
300
301 #[test]
302 fn create_repeating_timer() {
303 let service = TimerService::new();
304 let (id, _token) =
305 service.create_repeating(2, Duration::from_millis(500), text_payload("tick"));
306
307 let info = service.info(&id).unwrap();
308 assert_eq!(info.owner_pid, 2);
309 assert_eq!(info.repeat_interval_ms, Some(500));
310 }
311
312 #[test]
313 fn cancel_timer() {
314 let service = TimerService::new();
315 let (id, _token) = service.create_oneshot(1, Duration::from_secs(10), text_payload("x"));
316
317 assert!(service.cancel(&id));
318 let info = service.info(&id).unwrap();
319 assert!(info.cancelled);
320 }
321
322 #[test]
323 fn cancel_nonexistent() {
324 let service = TimerService::new();
325 assert!(!service.cancel("nope"));
326 }
327
328 #[test]
329 fn cancel_for_pid() {
330 let service = TimerService::new();
331 service.create_oneshot(1, Duration::from_secs(10), text_payload("a"));
332 service.create_oneshot(1, Duration::from_secs(20), text_payload("b"));
333 service.create_oneshot(2, Duration::from_secs(10), text_payload("c"));
334
335 let cancelled = service.cancel_for_pid(1);
336 assert_eq!(cancelled, 2);
337
338 let pid2_timers = service.list_for_pid(2);
340 assert_eq!(pid2_timers.len(), 1);
341 assert!(!pid2_timers[0].cancelled);
342 }
343
344 #[test]
345 fn cleanup_cancelled() {
346 let service = TimerService::new();
347 let (id, _) = service.create_oneshot(1, Duration::from_secs(10), text_payload("a"));
348 service.create_oneshot(2, Duration::from_secs(10), text_payload("b"));
349
350 service.cancel(&id);
351 let cleaned = service.cleanup_cancelled();
352 assert_eq!(cleaned, 1);
353 assert_eq!(service.len(), 1);
354 }
355
356 #[test]
357 fn collect_due_oneshot() {
358 let service = TimerService::new();
359 service.create_oneshot(5, Duration::from_millis(0), text_payload("now!"));
361
362 std::thread::sleep(Duration::from_millis(10));
364
365 let due = service.collect_due();
366 assert_eq!(due.len(), 1);
367 assert_eq!(due[0].0, 5); assert!(service.is_empty());
371 }
372
373 #[test]
374 fn collect_due_repeating_reschedules() {
375 let service = TimerService::new();
376 let (id, _) = service.create_repeating(3, Duration::from_millis(0), text_payload("repeat"));
377
378 std::thread::sleep(Duration::from_millis(10));
379
380 let due = service.collect_due();
381 assert_eq!(due.len(), 1);
382
383 assert!(service.info(&id).is_some());
385 assert_eq!(service.len(), 1);
386 }
387
388 #[test]
389 fn collect_due_skips_cancelled() {
390 let service = TimerService::new();
391 let (id, _) = service.create_oneshot(1, Duration::from_millis(0), text_payload("x"));
392 service.cancel(&id);
393
394 std::thread::sleep(Duration::from_millis(10));
395
396 let due = service.collect_due();
397 assert!(due.is_empty());
398 }
399
400 #[test]
401 fn collect_due_skips_future() {
402 let service = TimerService::new();
403 service.create_oneshot(1, Duration::from_secs(3600), text_payload("far future"));
404
405 let due = service.collect_due();
406 assert!(due.is_empty());
407 assert_eq!(service.len(), 1);
408 }
409
410 #[test]
411 fn list_and_list_info() {
412 let service = TimerService::new();
413 service.create_oneshot(1, Duration::from_secs(10), text_payload("a"));
414 service.create_oneshot(2, Duration::from_secs(20), text_payload("b"));
415
416 assert_eq!(service.list().len(), 2);
417 assert_eq!(service.list_info().len(), 2);
418 }
419
420 #[test]
421 fn list_for_pid() {
422 let service = TimerService::new();
423 service.create_oneshot(1, Duration::from_secs(10), text_payload("a"));
424 service.create_oneshot(1, Duration::from_secs(20), text_payload("b"));
425 service.create_oneshot(2, Duration::from_secs(10), text_payload("c"));
426
427 assert_eq!(service.list_for_pid(1).len(), 2);
428 assert_eq!(service.list_for_pid(2).len(), 1);
429 assert_eq!(service.list_for_pid(99).len(), 0);
430 }
431
432 #[test]
433 fn remove_timer() {
434 let service = TimerService::new();
435 let (id, _) = service.create_oneshot(1, Duration::from_secs(10), text_payload("x"));
436 assert_eq!(service.len(), 1);
437
438 let entry = service.remove(&id).unwrap();
439 assert_eq!(entry.owner_pid, 1);
440 assert!(service.is_empty());
441 }
442
443 #[test]
444 fn unique_ids() {
445 let service = TimerService::new();
446 let (id1, _) = service.create_oneshot(1, Duration::from_secs(10), text_payload("a"));
447 let (id2, _) = service.create_oneshot(1, Duration::from_secs(10), text_payload("b"));
448 assert_ne!(id1, id2);
449 }
450
451 #[test]
452 fn timer_info_serde_roundtrip() {
453 let info = TimerInfo {
454 id: "timer-1".into(),
455 owner_pid: 42,
456 fire_at: Utc::now(),
457 repeat_interval_ms: Some(1000),
458 cancelled: false,
459 };
460 let json = serde_json::to_string(&info).unwrap();
461 let restored: TimerInfo = serde_json::from_str(&json).unwrap();
462 assert_eq!(restored.id, "timer-1");
463 assert_eq!(restored.owner_pid, 42);
464 assert_eq!(restored.repeat_interval_ms, Some(1000));
465 }
466
467 #[test]
468 fn default_service() {
469 let service = TimerService::default();
470 assert!(service.is_empty());
471 }
472}