reovim_kernel/sched/timer.rs
1//! Timer mechanism for delayed and periodic work scheduling.
2//!
3//! Linux equivalent: `kernel/time/timer.c`
4//!
5//! This module provides timer primitives for scheduling work to execute
6//! after a delay or at regular intervals. Timers integrate with the
7//! [`Runtime`](super::Runtime) tick cycle and execute callbacks via the
8//! work queue for panic safety.
9//!
10//! # Architecture
11//!
12//! ```text
13//! ┌─────────────────────────────────────────────────────────────┐
14//! │ TimerWheel │
15//! │ ┌──────────────────────────────────────────────────────┐ │
16//! │ │ HashMap<TimerId, TimerEntry> │ │
17//! │ │ - deadline: Instant │ │
18//! │ │ - work: TimerWork (OneShot or Periodic) │ │
19//! │ └──────────────────────────────────────────────────────┘ │
20//! │ │ │
21//! │ │ tick(now) │
22//! │ v │
23//! │ ┌──────────────────────────────────────────────────────┐ │
24//! │ │ WorkQueue <- expired timers scheduled as Tasks │ │
25//! │ └──────────────────────────────────────────────────────┘ │
26//! └─────────────────────────────────────────────────────────────┘
27//! ```
28//!
29//! # Example
30//!
31//! ```
32//! use reovim_kernel::api::v1::*;
33//! use std::time::Duration;
34//! use std::sync::atomic::{AtomicUsize, Ordering};
35//! use std::sync::Arc;
36//!
37//! let mut runtime = Runtime::new();
38//! runtime.boot();
39//!
40//! // Schedule a one-shot timer
41//! let counter = Arc::new(AtomicUsize::new(0));
42//! let counter_clone = counter.clone();
43//! let _handle = runtime.schedule_delayed(Duration::from_millis(10), move || {
44//! counter_clone.fetch_add(1, Ordering::SeqCst);
45//! });
46//!
47//! // Timer fires when tick advances past deadline
48//! ```
49//!
50//! # Panic Safety
51//!
52//! Timer callbacks are executed via the work queue, which uses the executor's
53//! `catch_unwind` for panic safety. A panicking callback is marked as failed
54//! and does not repeat (for periodic timers).
55
56use std::{
57 collections::HashMap,
58 fmt,
59 sync::{
60 Arc, Weak,
61 atomic::{AtomicBool, AtomicU64, Ordering},
62 },
63 time::{Duration, Instant},
64};
65
66use crate::arch::sync::Mutex;
67
68use super::task::{Priority, Task};
69
70/// Unique timer identifier.
71///
72/// Timer IDs are monotonically increasing and never reused within a session.
73/// This follows the same pattern as [`TaskId`](super::TaskId) and
74/// [`BufferId`](crate::mm::BufferId).
75///
76/// # Example
77///
78/// ```
79/// use reovim_kernel::api::v1::*;
80///
81/// let id1 = TimerId::new();
82/// let id2 = TimerId::new();
83/// assert_ne!(id1, id2);
84/// assert!(id2.as_u64() > id1.as_u64());
85/// ```
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
87pub struct TimerId(u64);
88
89impl TimerId {
90 /// Create a new unique timer ID.
91 ///
92 /// IDs start at 1 and increment monotonically.
93 #[must_use]
94 pub fn new() -> Self {
95 static COUNTER: AtomicU64 = AtomicU64::new(1);
96 Self(COUNTER.fetch_add(1, Ordering::Relaxed))
97 }
98
99 /// Get the raw numeric value.
100 #[inline]
101 #[must_use]
102 pub const fn as_u64(self) -> u64 {
103 self.0
104 }
105
106 /// Create from raw value.
107 ///
108 /// Primarily for testing and deserialization.
109 #[inline]
110 #[must_use]
111 pub const fn from_raw(value: u64) -> Self {
112 Self(value)
113 }
114}
115
116impl Default for TimerId {
117 fn default() -> Self {
118 Self::new()
119 }
120}
121
122impl fmt::Display for TimerId {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 write!(f, "Timer({})", self.0)
125 }
126}
127
128/// Timer configuration.
129///
130/// Specifies when a timer should fire and at what priority the callback
131/// should execute. This struct is provided for advanced use cases where
132/// you need more control than `Runtime::schedule_delayed()` provides.
133///
134/// # Example
135///
136/// ```
137/// use reovim_kernel::api::v1::*;
138/// use std::time::Duration;
139///
140/// // One-shot timer after 100ms
141/// let config = TimerConfig {
142/// delay: Duration::from_millis(100),
143/// interval: None,
144/// priority: Priority::NORMAL,
145/// };
146///
147/// // Periodic timer every 50ms
148/// let periodic = TimerConfig {
149/// delay: Duration::from_millis(50),
150/// interval: Some(Duration::from_millis(50)),
151/// priority: Priority::LOW,
152/// };
153/// ```
154#[derive(Debug, Clone)]
155pub struct TimerConfig {
156 /// Initial delay before first execution.
157 pub delay: Duration,
158
159 /// Optional interval for periodic timers.
160 ///
161 /// If `Some`, the timer will reschedule after each execution.
162 /// If `None`, the timer is one-shot and removed after firing.
163 pub interval: Option<Duration>,
164
165 /// Priority for the scheduled task.
166 ///
167 /// Determines execution order relative to other work in the queue.
168 pub priority: Priority,
169}
170
171impl Default for TimerConfig {
172 fn default() -> Self {
173 Self {
174 delay: Duration::ZERO,
175 interval: None,
176 priority: Priority::NORMAL,
177 }
178 }
179}
180
181/// Type-erased one-shot timer callback.
182pub(super) type BoxedOneShotCallback = Box<dyn FnOnce() + Send + 'static>;
183
184/// Type-erased periodic timer callback.
185///
186/// Uses `Arc` instead of `Box` because periodic callbacks need to be cloned
187/// for each invocation.
188pub(super) type BoxedPeriodicCallback = Arc<dyn Fn() + Send + Sync + 'static>;
189
190/// Timer callback storage.
191///
192/// Distinguishes between one-shot (`FnOnce`) and periodic (`Fn`) callbacks.
193pub(super) enum TimerWork {
194 /// One-shot timer: callback is consumed on execution.
195 OneShot(Option<BoxedOneShotCallback>),
196
197 /// Periodic timer: callback can be called multiple times.
198 Periodic(BoxedPeriodicCallback),
199}
200
201impl fmt::Debug for TimerWork {
202 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 match self {
204 Self::OneShot(_) => write!(f, "OneShot(...)"),
205 Self::Periodic(_) => write!(f, "Periodic(...)"),
206 }
207 }
208}
209
210/// Internal timer entry in the wheel.
211pub(super) struct TimerEntry {
212 /// Unique identifier.
213 pub(super) id: TimerId,
214
215 /// When the timer should fire.
216 pub(super) deadline: Instant,
217
218 /// Interval for periodic rescheduling.
219 pub(super) interval: Option<Duration>,
220
221 /// Priority for the work task.
222 pub(super) priority: Priority,
223
224 /// The callback to execute.
225 pub(super) work: TimerWork,
226
227 /// Whether this timer has been cancelled.
228 pub(super) cancelled: AtomicBool,
229}
230
231impl TimerEntry {
232 /// Check if this timer has been cancelled.
233 #[inline]
234 pub(super) fn is_cancelled(&self) -> bool {
235 self.cancelled.load(Ordering::Acquire)
236 }
237
238 /// Mark this timer as cancelled.
239 #[inline]
240 pub(super) fn cancel(&self) {
241 self.cancelled.store(true, Ordering::Release);
242 }
243}
244
245impl fmt::Debug for TimerEntry {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 f.debug_struct("TimerEntry")
248 .field("id", &self.id)
249 .field("deadline", &self.deadline)
250 .field("interval", &self.interval)
251 .field("priority", &self.priority)
252 .field("work", &self.work)
253 .field("cancelled", &self.is_cancelled())
254 .finish()
255 }
256}
257
258/// Default maximum number of concurrent timers.
259pub const DEFAULT_MAX_TIMERS: usize = 1024;
260
261/// Timer wheel for managing scheduled timers.
262///
263/// The timer wheel stores active timers indexed by ID and checks for
264/// expired timers during each tick. Expired timers have their callbacks
265/// scheduled as tasks on the work queue.
266///
267/// # Thread Safety
268///
269/// The wheel uses a `Mutex<HashMap>` internally, making it safe to
270/// schedule and cancel from multiple threads.
271pub struct TimerWheel {
272 /// Active timers indexed by ID.
273 timers: Mutex<HashMap<TimerId, TimerEntry>>,
274
275 /// Next timer ID (atomic for thread-safe allocation).
276 next_id: AtomicU64,
277
278 /// Maximum number of timers allowed.
279 max_timers: usize,
280
281 /// Count of timers dropped due to capacity limit.
282 dropped: AtomicU64,
283}
284
285impl TimerWheel {
286 /// Create a new timer wheel with default capacity.
287 #[must_use]
288 pub fn new() -> Self {
289 Self::with_max_timers(DEFAULT_MAX_TIMERS)
290 }
291
292 /// Create a timer wheel with specified maximum timer count.
293 #[must_use]
294 pub fn with_max_timers(max_timers: usize) -> Self {
295 Self {
296 timers: Mutex::new(HashMap::new()),
297 next_id: AtomicU64::new(1),
298 max_timers,
299 dropped: AtomicU64::new(0),
300 }
301 }
302
303 /// Schedule a one-shot timer.
304 ///
305 /// Returns a handle that cancels the timer when dropped.
306 pub fn schedule_oneshot<F>(
307 self: &Arc<Self>,
308 delay: Duration,
309 priority: Priority,
310 work: F,
311 ) -> TimerHandle
312 where
313 F: FnOnce() + Send + 'static,
314 {
315 self.schedule_internal(delay, None, priority, TimerWork::OneShot(Some(Box::new(work))))
316 }
317
318 /// Schedule a periodic timer.
319 ///
320 /// Returns a handle that cancels the timer when dropped.
321 pub fn schedule_periodic<F>(
322 self: &Arc<Self>,
323 interval: Duration,
324 priority: Priority,
325 work: F,
326 ) -> TimerHandle
327 where
328 F: Fn() + Send + Sync + 'static,
329 {
330 self.schedule_internal(
331 interval,
332 Some(interval),
333 priority,
334 TimerWork::Periodic(Arc::new(work)),
335 )
336 }
337
338 /// Internal scheduling implementation.
339 fn schedule_internal(
340 self: &Arc<Self>,
341 delay: Duration,
342 interval: Option<Duration>,
343 priority: Priority,
344 work: TimerWork,
345 ) -> TimerHandle {
346 let id = TimerId(self.next_id.fetch_add(1, Ordering::Relaxed));
347 let deadline = Instant::now() + delay;
348
349 let mut timers = self.timers.lock();
350
351 // Check capacity limit
352 if timers.len() >= self.max_timers {
353 self.dropped.fetch_add(1, Ordering::Relaxed);
354 // Timer limit exceeded - return a failed handle
355 // Caller can check handle.is_failed() to detect this
356 return TimerHandle::failed(id);
357 }
358
359 let entry = TimerEntry {
360 id,
361 deadline,
362 interval,
363 priority,
364 work,
365 cancelled: AtomicBool::new(false),
366 };
367
368 timers.insert(id, entry);
369 drop(timers);
370
371 TimerHandle::new(id, Arc::downgrade(self))
372 }
373
374 /// Cancel a timer by ID.
375 ///
376 /// Returns `true` if the timer was found and cancelled, `false` if
377 /// it was already cancelled or has fired.
378 pub fn cancel(&self, id: TimerId) -> bool {
379 let timers = self.timers.lock();
380 timers.get(&id).is_some_and(|entry| {
381 if entry.is_cancelled() {
382 false
383 } else {
384 entry.cancel();
385 true
386 }
387 })
388 }
389
390 /// Check if a timer is still pending (scheduled but not yet fired).
391 ///
392 /// Returns `true` if the timer exists and hasn't been cancelled or fired.
393 pub fn is_pending(&self, id: TimerId) -> bool {
394 let timers = self.timers.lock();
395 timers.get(&id).is_some_and(|entry| !entry.is_cancelled())
396 }
397
398 /// Process expired timers and return tasks to execute.
399 ///
400 /// This should be called during each tick with the current time.
401 /// Returns a vector of tasks for expired timers.
402 #[cfg_attr(coverage_nightly, coverage(off))]
403 pub fn tick(&self, now: Instant) -> Vec<Task> {
404 let mut tasks = Vec::new();
405 let mut to_remove = Vec::new();
406 let mut to_reschedule = Vec::new();
407
408 {
409 let mut timers = self.timers.lock();
410
411 for (id, entry) in timers.iter_mut() {
412 // Skip cancelled timers
413 if entry.is_cancelled() {
414 to_remove.push(*id);
415 continue;
416 }
417
418 // Check if timer has expired
419 if entry.deadline <= now {
420 match &mut entry.work {
421 TimerWork::OneShot(work_opt) => {
422 // Take the FnOnce callback
423 if let Some(work) = work_opt.take() {
424 let task = Task::with_priority(entry.priority, work);
425 tasks.push(task);
426 }
427 to_remove.push(*id);
428 }
429 TimerWork::Periodic(work) => {
430 // Clone the Arc callback for execution
431 let work_clone = Arc::clone(work);
432 let task = Task::with_priority(entry.priority, move || work_clone());
433 tasks.push(task);
434
435 // Reschedule for next interval
436 if let Some(interval) = entry.interval {
437 to_reschedule.push((*id, now + interval));
438 }
439 }
440 }
441 }
442 }
443
444 // Remove completed/cancelled timers
445 for id in to_remove {
446 timers.remove(&id);
447 }
448
449 // Update deadlines for periodic timers
450 for (id, new_deadline) in to_reschedule {
451 if let Some(entry) = timers.get_mut(&id) {
452 entry.deadline = new_deadline;
453 }
454 }
455 }
456
457 tasks
458 }
459
460 /// Get the number of pending timers.
461 #[must_use]
462 pub fn pending_count(&self) -> usize {
463 self.timers.lock().len()
464 }
465
466 /// Get the number of timers dropped due to capacity limits.
467 #[must_use]
468 pub fn dropped_count(&self) -> u64 {
469 self.dropped.load(Ordering::Relaxed)
470 }
471}
472
473impl Default for TimerWheel {
474 fn default() -> Self {
475 Self::new()
476 }
477}
478
479impl fmt::Debug for TimerWheel {
480 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481 f.debug_struct("TimerWheel")
482 .field("pending", &self.pending_count())
483 .field("max_timers", &self.max_timers)
484 .field("dropped", &self.dropped_count())
485 .finish_non_exhaustive()
486 }
487}
488
489/// Handle to a scheduled timer with RAII cancellation.
490///
491/// When dropped, the handle automatically cancels the timer if it hasn't
492/// fired yet. This follows the RAII pattern for resource management.
493///
494/// # Example
495///
496/// ```
497/// use reovim_kernel::api::v1::*;
498/// use std::time::Duration;
499///
500/// let mut runtime = Runtime::new();
501/// runtime.boot();
502///
503/// {
504/// // Timer is scheduled
505/// let handle = runtime.schedule_delayed(Duration::from_secs(10), || {
506/// println!("This won't print");
507/// });
508/// // handle dropped here - timer is cancelled
509/// }
510///
511/// // Timer was cancelled, won't fire
512/// ```
513///
514/// # Manual Cancellation
515///
516/// You can also explicitly cancel via [`Runtime::cancel_timer`](super::Runtime::cancel_timer):
517///
518/// ```
519/// use reovim_kernel::api::v1::*;
520/// use std::time::Duration;
521///
522/// let mut runtime = Runtime::new();
523/// runtime.boot();
524///
525/// let handle = runtime.schedule_delayed(Duration::from_secs(10), || {});
526/// let id = handle.id();
527///
528/// // Explicit cancellation
529/// std::mem::forget(handle); // Prevent drop cancellation
530/// let was_pending = runtime.cancel_timer(id);
531/// ```
532pub struct TimerHandle {
533 /// The timer's unique identifier.
534 id: TimerId,
535
536 /// Weak reference to the timer wheel for cancellation.
537 ///
538 /// If the wheel has been dropped (runtime shutdown), cancellation
539 /// is a no-op since all timers are already gone.
540 wheel: Option<Weak<TimerWheel>>,
541
542 /// Whether this handle represents a failed scheduling attempt.
543 ///
544 /// When `max_timers` is exceeded, we return a "failed" handle that
545 /// doesn't actually have a timer to cancel.
546 failed: bool,
547}
548
549impl TimerHandle {
550 /// Create a new timer handle.
551 pub(crate) const fn new(id: TimerId, wheel: Weak<TimerWheel>) -> Self {
552 Self {
553 id,
554 wheel: Some(wheel),
555 failed: false,
556 }
557 }
558
559 /// Create a failed handle (timer wasn't actually scheduled).
560 pub(crate) const fn failed(id: TimerId) -> Self {
561 Self {
562 id,
563 wheel: None,
564 failed: true,
565 }
566 }
567
568 /// Get the timer's unique identifier.
569 #[inline]
570 #[must_use]
571 pub const fn id(&self) -> TimerId {
572 self.id
573 }
574
575 /// Check if this handle represents a failed scheduling attempt.
576 ///
577 /// Returns `true` if the timer was not actually scheduled (e.g., due
578 /// to hitting the `max_timers` limit).
579 #[inline]
580 #[must_use]
581 pub const fn is_failed(&self) -> bool {
582 self.failed
583 }
584
585 /// Prevent automatic cancellation on drop.
586 ///
587 /// After calling this, dropping the handle will NOT cancel the timer.
588 /// Use this when you want the timer to fire regardless of handle lifetime.
589 ///
590 /// Returns the timer ID for manual cancellation if needed later.
591 #[must_use]
592 pub fn detach(mut self) -> TimerId {
593 let id = self.id;
594 self.wheel = None; // Prevent Drop from cancelling
595 id
596 }
597}
598
599impl Drop for TimerHandle {
600 fn drop(&mut self) {
601 // If we have a wheel reference and it's still alive, cancel the timer
602 if let Some(ref weak) = self.wheel
603 && let Some(wheel) = weak.upgrade()
604 {
605 wheel.cancel(self.id);
606 }
607 }
608}
609
610impl fmt::Debug for TimerHandle {
611 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
612 f.debug_struct("TimerHandle")
613 .field("id", &self.id)
614 .field("failed", &self.failed)
615 .field("wheel_alive", &self.wheel.as_ref().is_some_and(|w| w.strong_count() > 0))
616 .finish()
617 }
618}