1use std::{
42 num::NonZeroU64,
43 sync::{
44 Arc,
45 atomic::{self, AtomicU8, AtomicU64},
46 },
47};
48
49use nautilus_core::{
50 DurationNanos, UUID4, UnixNanos,
51 correctness::{FAILED, check_valid_string_utf8},
52 datetime::floor_to_nearest_microsecond,
53 time::get_atomic_clock_realtime,
54};
55use ustr::Ustr;
56
57use super::dst::{
58 self,
59 task::JoinHandle,
60 time::{Duration, Instant},
61};
62#[cfg(not(all(feature = "simulation", madsim)))]
63use super::runtime::get_runtime;
64use crate::{
65 runner::{
66 TimeEventCallbackLease, TimeEventCallbackToken, TimeEventMessage, TimeEventMessageFactory,
67 TimeEventSender, register_time_event_callback,
68 },
69 timer::{TimeEvent, TimeEventCallback, Timer},
70};
71
72const TASK_ACTIVE: u8 = 0;
73const TASK_FIRING: u8 = 1;
74const TASK_RETIRED: u8 = 2;
75const TASK_EXHAUSTED: u8 = 3;
76
77#[derive(Debug)]
86pub struct LiveTimer {
87 pub name: Ustr,
89 pub interval_ns: NonZeroU64,
91 pub start_time_ns: UnixNanos,
93 pub stop_time_ns: Option<UnixNanos>,
95 pub fire_immediately: bool,
97 next_time_ns: Arc<AtomicU64>,
98 callback: OwnerCallback,
99 task_handle: Option<JoinHandle<()>>,
100 task_state: Option<Arc<TimerTaskState>>,
101 exhausted: bool,
102 canceled: bool,
103 sender: Option<Arc<dyn TimeEventSender>>,
104}
105
106impl LiveTimer {
107 #[must_use]
115 pub fn new(
116 name: Ustr,
117 interval_ns: NonZeroU64,
118 start_time_ns: UnixNanos,
119 stop_time_ns: Option<UnixNanos>,
120 callback: TimeEventCallback,
121 fire_immediately: bool,
122 sender: Option<Arc<dyn TimeEventSender>>,
123 ) -> Self {
124 check_valid_string_utf8(name, stringify!(name)).expect(FAILED);
125
126 let next_time_ns = if fire_immediately {
127 start_time_ns.as_u64()
128 } else {
129 (start_time_ns + DurationNanos::new(interval_ns.get())).as_u64()
130 };
131
132 log::trace!("Creating timer '{name}'");
133
134 let owner_callback = if sender.is_some() {
135 if callback.is_local() {
136 OwnerCallback::Registered {
137 token: register_time_event_callback(callback.clone()),
138 callback,
139 }
140 } else {
141 OwnerCallback::Direct(TimeEventMessageFactory::new(&callback))
142 }
143 } else {
144 OwnerCallback::Senderless(callback)
145 };
146
147 Self {
148 name,
149 interval_ns,
150 start_time_ns,
151 stop_time_ns,
152 fire_immediately,
153 next_time_ns: Arc::new(AtomicU64::new(next_time_ns)),
154 callback: owner_callback,
155 task_handle: None,
156 task_state: None,
157 exhausted: false,
158 canceled: false,
159 sender,
160 }
161 }
162
163 #[must_use]
168 pub fn next_time_ns(&self) -> UnixNanos {
169 UnixNanos::from(self.next_time_ns.load(atomic::Ordering::SeqCst))
170 }
171
172 #[must_use]
177 pub fn is_expired(&self) -> bool {
178 self.canceled
179 || self
180 .task_handle
181 .as_ref()
182 .is_some_and(JoinHandle::is_finished)
183 }
184
185 #[allow(unused_variables)]
201 pub fn start(&mut self) {
202 if let OwnerCallback::Senderless(callback) = &self.callback {
203 match callback {
204 #[cfg(feature = "python")]
205 TimeEventCallback::Python(_) => {}
206 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
207 panic!("timer event sender was unset for Rust callback system");
208 }
209 }
210 }
211
212 let event_name = self.name;
213 let stop_time_ns = self.stop_time_ns;
214 let interval_ns = DurationNanos::new(self.interval_ns.get());
215
216 let retired_task = self.retire_task();
217
218 if self.exhausted {
219 return;
220 }
221
222 let mut observed_next = retired_task.map_or_else(
223 || self.next_time_ns.load(atomic::Ordering::SeqCst),
224 |retirement| retirement.next_time_ns,
225 );
226
227 if let Some(handle) = self.task_handle.take() {
230 self.close_registered_callback();
231 handle.abort();
232 }
233
234 let worker_dispatch = match &mut self.callback {
235 OwnerCallback::Registered { token, callback } => {
236 if token.is_closed() {
240 *token = register_time_event_callback(callback.clone());
241 }
242 WorkerDispatch::Registered(token.clone())
243 }
244 OwnerCallback::Direct(factory) => WorkerDispatch::Direct(factory.clone()),
245 OwnerCallback::Senderless(callback) => match callback {
246 #[cfg(feature = "python")]
247 TimeEventCallback::Python(callback) => {
248 WorkerDispatch::SenderlessPython(callback.clone())
249 }
250 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
251 unreachable!("senderless Rust callback rejected at start")
252 }
253 },
254 };
255
256 let clock = get_atomic_clock_realtime();
258 let now_ns = clock.get_time_ns();
259
260 let now_raw = now_ns.as_u64();
262
263 if should_adjust_past_due_time(observed_next, now_ns, stop_time_ns) {
264 if observed_next < now_raw {
265 let original = UnixNanos::from(observed_next);
266 log::warn!(
267 "Timer '{event_name}' alert time {} was in the past, adjusted to current time for immediate fire",
268 original.to_rfc3339(),
269 );
270 }
271
272 observed_next = now_raw;
273 }
274
275 let mut next_time_ns = normalize_start_time_ns(observed_next, now_ns, stop_time_ns);
277 let next_time_atomic = Arc::new(AtomicU64::new(next_time_ns.as_u64()));
278 let task_state = Arc::new(TimerTaskState::new(next_time_ns.as_u64()));
279 self.next_time_ns = next_time_atomic.clone();
280 self.task_state = Some(task_state.clone());
281
282 let sender = self.sender.clone();
283 let now_ns = clock.get_time_ns();
284 let start = Instant::now() + timer_start_delay(next_time_ns, now_ns);
285
286 let task = async move {
287 let clock = get_atomic_clock_realtime();
288
289 let mut timer = dst::time::interval_at(start, Duration::from(interval_ns));
290
291 loop {
292 if !should_fire_scheduled_time(next_time_ns, stop_time_ns) {
297 if let (Some(sender), WorkerDispatch::Registered(token)) =
298 (sender.as_ref(), &worker_dispatch)
299 && let Some(lease) = token.acquire()
300 {
301 token.close();
302 let now_ns = clock.get_time_ns();
303 let event = TimeEvent::new(event_name, UUID4::new(), next_time_ns, now_ns);
304 sender.send(TimeEventMessage::cleanup(event, lease));
305 }
306 break; }
308
309 timer.tick().await;
312 let now_ns = clock.get_time_ns();
313
314 let event = TimeEvent::new(event_name, UUID4::new(), next_time_ns, now_ns);
315
316 let following_next_time_ns = next_time_ns.checked_add(interval_ns);
319 let expires_after_fire = expires_after_scheduled_time(next_time_ns, stop_time_ns)
320 || following_next_time_ns.is_none();
321
322 let registered_lease = if let WorkerDispatch::Registered(token) = &worker_dispatch {
327 match task_state.reserve_registered_fire(
328 token,
329 following_next_time_ns.map(|time| time.as_u64()),
330 ) {
331 Some(lease) => Some(lease),
332 None => break,
333 }
334 } else {
335 if !task_state.reserve_fire(following_next_time_ns.map(|time| time.as_u64())) {
336 break;
337 }
338 None
339 };
340
341 if sender.is_some()
342 && let Some(following_next_time_ns) = following_next_time_ns
343 {
344 next_time_atomic
345 .store(following_next_time_ns.as_u64(), atomic::Ordering::SeqCst);
346 }
347
348 match (&sender, &worker_dispatch) {
349 (Some(sender), WorkerDispatch::Direct(factory)) => {
350 sender.send(factory.message(event));
351 }
352 (Some(sender), WorkerDispatch::Registered(token)) => {
353 let lease =
354 registered_lease.expect("registered callback lease was not acquired");
355
356 if expires_after_fire {
357 token.close();
358 }
359 sender.send(TimeEventMessage::registered(event, lease));
360 }
361 #[cfg(feature = "python")]
362 (None, WorkerDispatch::SenderlessPython(callback)) => callback.call(event),
363 _ => unreachable!("timer callback dispatch did not match its sender"),
364 }
365
366 if sender.is_none()
367 && let Some(following_next_time_ns) = following_next_time_ns
368 {
369 next_time_atomic
370 .store(following_next_time_ns.as_u64(), atomic::Ordering::SeqCst);
371 }
372
373 if let Some(following_next_time_ns) = following_next_time_ns {
374 next_time_ns = following_next_time_ns;
375 }
376
377 if expires_after_fire {
378 break; }
380 }
381 };
382
383 #[cfg(all(feature = "simulation", madsim))]
384 let handle = dst::task::spawn(task);
385 #[cfg(not(all(feature = "simulation", madsim)))]
386 let handle = get_runtime().spawn(task);
387
388 self.task_handle = Some(handle);
389 self.canceled = false;
390 }
391
392 pub fn cancel(&mut self) {
396 log::trace!("Cancel timer '{}'", self.name);
397
398 self.close_registered_callback();
399
400 self.retire_task();
401
402 if let Some(handle) = self.task_handle.take() {
403 handle.abort();
404 }
405 self.canceled = true;
406 }
407
408 fn close_registered_callback(&self) {
409 if let OwnerCallback::Registered { token, .. } = &self.callback {
410 token.close();
411 }
412 }
413
414 fn retire_task(&mut self) -> Option<TimerTaskRetirement> {
415 let task_state = self.task_state.take()?;
416 let retirement = task_state.retire();
417 self.exhausted |= retirement.exhausted;
418 self.next_time_ns
419 .store(retirement.next_time_ns, atomic::Ordering::SeqCst);
420 Some(retirement)
421 }
422}
423
424impl Timer for LiveTimer {
425 fn is_expired(&self) -> bool {
426 Self::is_expired(self)
427 }
428
429 fn cancel(&mut self) {
430 Self::cancel(self);
431 }
432}
433
434impl Drop for LiveTimer {
435 fn drop(&mut self) {
436 self.close_registered_callback();
437 self.retire_task();
438
439 if let Some(handle) = self.task_handle.take() {
440 handle.abort();
441 }
442 }
443}
444
445fn should_fire_scheduled_time(next_time_ns: UnixNanos, stop_time_ns: Option<UnixNanos>) -> bool {
446 stop_time_ns.is_none_or(|stop_time_ns| next_time_ns <= stop_time_ns)
447}
448
449fn expires_after_scheduled_time(next_time_ns: UnixNanos, stop_time_ns: Option<UnixNanos>) -> bool {
450 stop_time_ns == Some(next_time_ns)
451}
452
453fn is_stop_boundary(next_time_ns: u64, stop_time_ns: Option<UnixNanos>) -> bool {
454 stop_time_ns == Some(UnixNanos::from(next_time_ns))
455}
456
457fn should_adjust_past_due_time(
458 observed_next: u64,
459 now_ns: UnixNanos,
460 stop_time_ns: Option<UnixNanos>,
461) -> bool {
462 observed_next <= now_ns.as_u64() && !is_stop_boundary(observed_next, stop_time_ns)
463}
464
465fn normalize_start_time_ns(
466 observed_next: u64,
467 now_ns: UnixNanos,
468 stop_time_ns: Option<UnixNanos>,
469) -> UnixNanos {
470 if is_stop_boundary(observed_next, stop_time_ns) {
471 return UnixNanos::from(observed_next);
472 }
473
474 let now_raw = now_ns.as_u64();
475 let start_time_ns = if observed_next <= now_raw {
476 now_raw
477 } else {
478 observed_next
479 };
480
481 UnixNanos::from(floor_to_nearest_microsecond(start_time_ns))
482}
483
484fn timer_start_delay(next_time_ns: UnixNanos, now_ns: UnixNanos) -> Duration {
485 Duration::from(next_time_ns.saturating_duration_since(now_ns))
486}
487
488#[derive(Debug)]
489struct TimerTaskState {
490 status: AtomicU8,
491 next_time_ns: AtomicU64,
492}
493
494#[derive(Clone, Copy, Debug, PartialEq, Eq)]
495struct TimerTaskRetirement {
496 next_time_ns: u64,
497 exhausted: bool,
498}
499
500impl TimerTaskState {
501 fn new(next_time_ns: u64) -> Self {
502 Self {
503 status: AtomicU8::new(TASK_ACTIVE),
504 next_time_ns: AtomicU64::new(next_time_ns),
505 }
506 }
507
508 fn reserve_registered_fire(
509 &self,
510 token: &TimeEventCallbackToken,
511 following_next_time_ns: Option<u64>,
512 ) -> Option<TimeEventCallbackLease> {
513 let lease = token.acquire()?;
514 self.reserve_fire(following_next_time_ns).then_some(lease)
515 }
516
517 fn reserve_fire(&self, following_next_time_ns: Option<u64>) -> bool {
518 if self
519 .status
520 .compare_exchange(
521 TASK_ACTIVE,
522 TASK_FIRING,
523 atomic::Ordering::SeqCst,
524 atomic::Ordering::SeqCst,
525 )
526 .is_err()
527 {
528 return false;
529 }
530
531 if let Some(following_next_time_ns) = following_next_time_ns {
532 self.next_time_ns
533 .store(following_next_time_ns, atomic::Ordering::SeqCst);
534 self.status.store(TASK_ACTIVE, atomic::Ordering::SeqCst);
535 } else {
536 self.status.store(TASK_EXHAUSTED, atomic::Ordering::SeqCst);
537 }
538 true
539 }
540
541 fn retire(&self) -> TimerTaskRetirement {
542 let exhausted = loop {
543 match self.status.compare_exchange(
544 TASK_ACTIVE,
545 TASK_RETIRED,
546 atomic::Ordering::SeqCst,
547 atomic::Ordering::SeqCst,
548 ) {
549 Ok(_) | Err(TASK_RETIRED) => break false,
550 Err(TASK_EXHAUSTED) => break true,
551 Err(TASK_FIRING) => std::hint::spin_loop(),
553 Err(status) => unreachable!("invalid timer task state {status}"),
554 }
555 };
556
557 TimerTaskRetirement {
558 next_time_ns: self.next_time_ns.load(atomic::Ordering::SeqCst),
559 exhausted,
560 }
561 }
562}
563
564#[derive(Debug)]
565enum OwnerCallback {
566 Direct(TimeEventMessageFactory),
567 Registered {
570 token: TimeEventCallbackToken,
571 callback: TimeEventCallback,
572 },
573 Senderless(TimeEventCallback),
574}
575
576#[derive(Clone, Debug)]
577enum WorkerDispatch {
578 Direct(TimeEventMessageFactory),
579 Registered(TimeEventCallbackToken),
580 #[cfg(feature = "python")]
581 SenderlessPython(Arc<crate::timer::PythonTimeEventCallback>),
582}
583
584#[cfg(test)]
585mod tests {
586 #[cfg(not(all(feature = "simulation", madsim)))]
587 use std::rc::Rc;
588 #[cfg(all(feature = "python", not(all(feature = "simulation", madsim))))]
589 use std::sync::{OnceLock, atomic::AtomicU64};
590 use std::{
591 num::NonZeroU64,
592 sync::{
593 Arc,
594 atomic::{AtomicUsize, Ordering},
595 },
596 };
597 #[cfg(any(feature = "python", not(all(feature = "simulation", madsim))))]
598 use std::{sync::mpsc, time::Duration as StdDuration};
599
600 use nautilus_core::{UnixNanos, time::get_atomic_clock_realtime};
601 #[cfg(not(all(feature = "simulation", madsim)))]
602 use parking_lot::Mutex;
603 #[cfg(feature = "python")]
604 use pyo3::{
605 Python,
606 types::{PyAnyMethods, PyList, PyListMethods},
607 };
608 use rstest::*;
609 use ustr::Ustr;
610
611 use super::LiveTimer;
612 #[cfg(not(all(feature = "simulation", madsim)))]
613 use crate::runner::register_time_event_callback;
614 #[cfg(not(all(feature = "simulation", madsim)))]
615 use crate::testing::wait_until;
616 use crate::{
617 runner::{TimeEventMessage, TimeEventSender},
618 timer::TimeEventCallback,
619 };
620
621 #[cfg(any(feature = "python", not(all(feature = "simulation", madsim))))]
622 #[derive(Debug)]
623 struct ChannelSender {
624 tx: mpsc::Sender<TimeEventMessage>,
625 }
626
627 #[cfg(any(feature = "python", not(all(feature = "simulation", madsim))))]
628 impl TimeEventSender for ChannelSender {
629 fn send(&self, message: TimeEventMessage) {
630 self.tx.send(message).expect("message should send");
631 }
632 }
633
634 #[cfg(not(all(feature = "simulation", madsim)))]
635 #[derive(Debug)]
636 struct PausingChannelSender {
637 tx: mpsc::Sender<TimeEventMessage>,
638 release_rx: Mutex<mpsc::Receiver<()>>,
639 }
640
641 #[cfg(not(all(feature = "simulation", madsim)))]
642 impl TimeEventSender for PausingChannelSender {
643 fn send(&self, message: TimeEventMessage) {
644 self.tx.send(message).expect("message should send");
645 self.release_rx
646 .lock()
647 .recv()
648 .expect("timer send should release");
649 }
650 }
651
652 #[cfg(all(feature = "simulation", madsim))]
653 #[derive(Debug)]
654 struct CountingSender {
655 count: Arc<AtomicUsize>,
656 }
657
658 #[cfg(all(feature = "simulation", madsim))]
659 impl TimeEventSender for CountingSender {
660 fn send(&self, _message: TimeEventMessage) {
661 self.count.fetch_add(1, Ordering::Relaxed);
662 }
663 }
664
665 #[rstest]
666 #[case::unbounded(100, None, true, false)]
667 #[case::past_stop(110, Some(100), false, false)]
668 #[case::before_stop(90, Some(100), true, false)]
669 #[case::at_stop(100, Some(100), true, true)]
670 fn test_live_timer_stop_bound(
671 #[case] next_time_ns: u64,
672 #[case] stop_time_ns: Option<u64>,
673 #[case] should_fire: bool,
674 #[case] expires: bool,
675 ) {
676 let next_time_ns = UnixNanos::from(next_time_ns);
677 let stop_time_ns = stop_time_ns.map(UnixNanos::from);
678
679 assert_eq!(
680 super::should_fire_scheduled_time(next_time_ns, stop_time_ns),
681 should_fire
682 );
683 assert_eq!(
684 super::expires_after_scheduled_time(next_time_ns, stop_time_ns),
685 expires
686 );
687 }
688
689 #[rstest]
690 #[case::stop_boundary(100, 110, 100, false)]
691 #[case::before_stop(90, 110, 120, true)]
692 fn test_live_timer_past_due_adjustment(
693 #[case] observed_next: u64,
694 #[case] now: u64,
695 #[case] stop_time_ns: u64,
696 #[case] expected: bool,
697 ) {
698 assert_eq!(
699 super::should_adjust_past_due_time(
700 observed_next,
701 UnixNanos::from(now),
702 Some(UnixNanos::from(stop_time_ns)),
703 ),
704 expected
705 );
706 }
707
708 #[rstest]
709 #[case::past_due(1_234_567, 2_345_678, None, 2_345_000)]
710 #[case::future(3_456_789, 2_345_678, None, 3_456_000)]
711 #[case::stop_boundary(1_234_567, 2_345_678, Some(1_234_567), 1_234_567)]
712 fn test_live_timer_start_time_normalization(
713 #[case] observed_next: u64,
714 #[case] now: u64,
715 #[case] stop_time_ns: Option<u64>,
716 #[case] expected: u64,
717 ) {
718 assert_eq!(
719 super::normalize_start_time_ns(
720 observed_next,
721 UnixNanos::from(now),
722 stop_time_ns.map(UnixNanos::from),
723 ),
724 UnixNanos::from(expected)
725 );
726 }
727
728 #[rstest]
729 #[case::full(12_000_000, 10_000_000, 2_000_000)]
730 #[case::sub_millisecond(10_500_000, 10_000_000, 500_000)]
731 fn test_live_timer_start_delay(
732 #[case] next_time_ns: u64,
733 #[case] now: u64,
734 #[case] expected_ns: u64,
735 ) {
736 assert_eq!(
737 super::timer_start_delay(UnixNanos::from(next_time_ns), UnixNanos::from(now)),
738 tokio::time::Duration::from_nanos(expected_ns)
739 );
740 }
741
742 #[rstest]
743 #[case::following_schedule(Some(200))]
744 #[case::exhaustion(None)]
745 fn test_timer_task_retirement_prevents_a_late_fire(
746 #[case] following_next_time_ns: Option<u64>,
747 ) {
748 let state = super::TimerTaskState::new(100);
749
750 let retirement = state.retire();
751 let reserved = state.reserve_fire(following_next_time_ns);
752
753 assert_eq!(retirement.next_time_ns, 100);
754 assert!(!retirement.exhausted);
755 assert!(!reserved);
756 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 100);
757 }
758
759 #[rstest]
760 fn test_timer_task_retirement_preserves_a_reserved_fire() {
761 let state = super::TimerTaskState::new(100);
762
763 let reserved = state.reserve_fire(Some(200));
764 let retirement = state.retire();
765
766 assert!(reserved);
767 assert_eq!(retirement.next_time_ns, 200);
768 assert!(!retirement.exhausted);
769 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 200);
770 }
771
772 #[rstest]
773 fn test_timer_task_retirement_preserves_exhaustion() {
774 let state = super::TimerTaskState::new(100);
775
776 let reserved = state.reserve_fire(None);
777 let retirement = state.retire();
778
779 assert!(reserved);
780 assert_eq!(retirement.next_time_ns, 100);
781 assert!(retirement.exhausted);
782 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 100);
783 }
784
785 #[cfg(not(all(feature = "simulation", madsim)))]
786 #[rstest]
787 fn test_closed_registered_callback_does_not_reserve_fire() {
788 let state = super::TimerTaskState::new(100);
789 let callback = TimeEventCallback::RustLocal(Rc::new(|_| {}));
790 let token = register_time_event_callback(callback);
791 token.close();
792
793 let lease = state.reserve_registered_fire(&token, Some(200));
794 let retirement = state.retire();
795
796 assert!(lease.is_none());
797 assert_eq!(retirement.next_time_ns, 100);
798 assert!(!retirement.exhausted);
799 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 100);
800 }
801
802 #[rstest]
803 #[case::immediate(true, 100)]
804 #[case::after_interval(false, 1_100)]
805 fn test_live_timer_fire_immediately(
806 #[case] fire_immediately: bool,
807 #[case] expected_next_time_ns: u64,
808 ) {
809 let timer = LiveTimer::new(
810 Ustr::from("TEST_TIMER"),
811 NonZeroU64::new(1000).unwrap(),
812 UnixNanos::from(100),
813 None,
814 TimeEventCallback::from(|_| {}),
815 fire_immediately,
816 None,
817 );
818
819 assert_eq!(timer.fire_immediately, fire_immediately);
820 assert_eq!(timer.next_time_ns(), UnixNanos::from(expected_next_time_ns));
821 }
822
823 #[rstest]
824 #[should_panic(expected = "timer event sender was unset for Rust callback system")]
825 fn test_live_timer_start_panics_on_senderless_rust_callback() {
826 let now = get_atomic_clock_realtime().get_time_ns();
827 let mut timer = LiveTimer::new(
828 Ustr::from("SENDERLESS_RUST"),
829 NonZeroU64::new(1_000_000).unwrap(),
830 now,
831 None,
832 TimeEventCallback::from(|_| {}),
833 false,
834 None, );
836
837 timer.start();
838 }
839
840 #[cfg(not(all(feature = "simulation", madsim)))]
841 #[rstest]
842 fn test_live_timer_uses_global_runtime() {
843 let (tx, rx) = mpsc::channel();
844 let sender = Arc::new(ChannelSender { tx });
845 let now = get_atomic_clock_realtime().get_time_ns();
846 let mut timer = LiveTimer::new(
847 Ustr::from("LIVE_TIMER"),
848 NonZeroU64::new(1_000_000).unwrap(),
849 now,
850 Some(now),
851 TimeEventCallback::from(|_| {}),
852 true,
853 Some(sender),
854 );
855
856 timer.start();
857 let message = rx
858 .recv_timeout(StdDuration::from_secs(1))
859 .expect("timer message should arrive on the global runtime");
860 wait_until(|| timer.is_expired(), StdDuration::from_secs(1));
861
862 assert_eq!(message.event().ts_event, now);
863 assert!(timer.is_expired());
864 }
865
866 #[cfg(not(all(feature = "simulation", madsim)))]
867 #[rstest]
868 fn test_live_timer_expires_after_terminal_event() {
869 let (tx, rx) = mpsc::channel();
870 let sender = Arc::new(ChannelSender { tx });
871 let now = get_atomic_clock_realtime().get_time_ns();
872 let mut timer = LiveTimer::new(
873 Ustr::from("TERMINAL_TIMER"),
874 NonZeroU64::new(u64::MAX).unwrap(),
875 now,
876 None,
877 TimeEventCallback::from(|_| {}),
878 true,
879 Some(sender),
880 );
881
882 timer.start();
883 let scheduled_time = timer.next_time_ns();
884 let message = rx
885 .recv_timeout(StdDuration::from_secs(1))
886 .expect("terminal timer event should arrive");
887 wait_until(|| timer.is_expired(), StdDuration::from_secs(1));
888
889 assert_eq!(message.event().ts_event, scheduled_time);
890 assert_eq!(timer.next_time_ns(), scheduled_time);
891 assert!(timer.is_expired());
892
893 timer.start();
894 assert!(rx.recv_timeout(StdDuration::from_millis(10)).is_err());
895 assert_eq!(timer.next_time_ns(), scheduled_time);
896 assert!(timer.is_expired());
897 }
898
899 #[cfg(not(all(feature = "simulation", madsim)))]
900 #[rstest]
901 fn test_live_timer_registered_callback_expires_after_terminal_event() {
902 let (tx, rx) = mpsc::channel();
903 let sender = Arc::new(ChannelSender { tx });
904 let count = Rc::new(std::cell::Cell::new(0));
905 let callback_count = count.clone();
906 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
907 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
908 let callback_weak = Rc::downgrade(&callback);
909 let now = get_atomic_clock_realtime().get_time_ns();
910 let mut timer = LiveTimer::new(
911 Ustr::from("TERMINAL_LOCAL_TIMER"),
912 NonZeroU64::new(u64::MAX).unwrap(),
913 now,
914 None,
915 TimeEventCallback::RustLocal(callback),
916 true,
917 Some(sender),
918 );
919
920 timer.start();
921 let scheduled_time = timer.next_time_ns();
922 let message = rx
923 .recv_timeout(StdDuration::from_secs(1))
924 .expect("registered terminal timer event should arrive");
925 wait_until(|| timer.is_expired(), StdDuration::from_secs(1));
926
927 assert_eq!(message.event().ts_event, scheduled_time);
928 assert!(message.dispatch());
929 assert_eq!(count.get(), 1);
930 assert_eq!(timer.next_time_ns(), scheduled_time);
931 assert!(timer.is_expired());
932
933 timer.start();
934 let token_closed = match &timer.callback {
935 super::OwnerCallback::Registered { token, .. } => token.is_closed(),
936 _ => false,
937 };
938 assert!(rx.recv_timeout(StdDuration::from_millis(10)).is_err());
939 assert!(token_closed);
940 assert_eq!(timer.next_time_ns(), scheduled_time);
941 assert!(timer.is_expired());
942 assert!(callback_weak.upgrade().is_some());
943
944 drop(timer);
945 assert!(callback_weak.upgrade().is_none());
946 }
947
948 #[cfg(not(all(feature = "simulation", madsim)))]
949 #[rstest]
950 fn test_live_timer_dispatches_rust_local_callback_on_owner_thread() {
951 let (tx, rx) = mpsc::channel();
952 let sender = Arc::new(ChannelSender { tx });
953 let count = Rc::new(std::cell::Cell::new(0));
954 let callback_count = count.clone();
955 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
956 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
957 let now = get_atomic_clock_realtime().get_time_ns();
958 let mut timer = LiveTimer::new(
959 Ustr::from("LOCAL_TIMER"),
960 NonZeroU64::new(1_000_000).unwrap(),
961 now,
962 Some(now),
963 TimeEventCallback::RustLocal(callback),
964 true,
965 Some(sender),
966 );
967
968 timer.start();
969 let message = rx
970 .recv_timeout(StdDuration::from_secs(1))
971 .expect("registered timer message should arrive");
972
973 assert!(message.dispatch());
974 assert_eq!(count.get(), 1);
975 }
976
977 #[cfg(not(all(feature = "simulation", madsim)))]
978 #[rstest]
979 fn test_live_timer_cancel_preserves_queued_rust_local_callback_lease() {
980 let (tx, rx) = mpsc::channel();
981 let (release_tx, release_rx) = mpsc::channel();
982 let sender = Arc::new(PausingChannelSender {
983 tx,
984 release_rx: Mutex::new(release_rx),
985 });
986 let count = Rc::new(std::cell::Cell::new(0));
987 let callback_count = count.clone();
988 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
989 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
990 let callback_weak = Rc::downgrade(&callback);
991 let now = get_atomic_clock_realtime().get_time_ns();
992 let mut timer = LiveTimer::new(
993 Ustr::from("CANCEL_QUEUED"),
994 NonZeroU64::new(1_000_000).unwrap(),
995 now,
996 None,
997 TimeEventCallback::RustLocal(callback),
998 true,
999 Some(sender),
1000 );
1001
1002 timer.start();
1003 let message = rx
1004 .recv_timeout(StdDuration::from_secs(1))
1005 .expect("registered timer message should arrive");
1006 timer.cancel();
1007 release_tx.send(()).expect("timer send should release");
1008
1009 assert!(callback_weak.upgrade().is_some());
1010 assert!(message.dispatch());
1011 assert_eq!(count.get(), 1);
1012
1013 drop(timer);
1016 assert!(callback_weak.upgrade().is_none());
1017 }
1018
1019 #[cfg(not(all(feature = "simulation", madsim)))]
1020 #[rstest]
1021 fn test_live_timer_cancel_preserves_queued_direct_callback() {
1022 let (tx, rx) = mpsc::channel();
1023 let sender = Arc::new(ChannelSender { tx });
1024 let count = Arc::new(AtomicUsize::new(0));
1025 let callback_count = count.clone();
1026 let now = get_atomic_clock_realtime().get_time_ns();
1027 let mut timer = LiveTimer::new(
1028 Ustr::from("CANCEL_QUEUED_DIRECT"),
1029 NonZeroU64::new(1_000_000).unwrap(),
1030 now,
1031 None,
1032 TimeEventCallback::from(move |_| {
1033 callback_count.fetch_add(1, Ordering::Relaxed);
1034 }),
1035 true,
1036 Some(sender),
1037 );
1038
1039 timer.start();
1040 let message = rx
1041 .recv_timeout(StdDuration::from_secs(1))
1042 .expect("direct timer message should arrive");
1043 timer.cancel();
1044
1045 assert!(message.dispatch());
1046 assert_eq!(count.load(Ordering::Relaxed), 1);
1047 }
1048
1049 #[cfg(not(all(feature = "simulation", madsim)))]
1050 #[rstest]
1051 fn test_live_timer_restart_after_cancel_re_registers_rust_local_callback() {
1052 let (tx, rx) = mpsc::channel();
1053 let sender = Arc::new(ChannelSender { tx });
1054 let count = Rc::new(std::cell::Cell::new(0));
1055 let callback_count = count.clone();
1056 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1057 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1058 let now = get_atomic_clock_realtime().get_time_ns();
1059 let mut timer = LiveTimer::new(
1060 Ustr::from("RESTART_TIMER"),
1061 NonZeroU64::new(1_000_000).unwrap(),
1062 now,
1063 None,
1064 TimeEventCallback::RustLocal(callback),
1065 true,
1066 Some(sender),
1067 );
1068
1069 timer.start();
1070 let first = rx
1071 .recv_timeout(StdDuration::from_secs(1))
1072 .expect("first registered timer message should arrive");
1073 timer.cancel();
1074 assert!(first.dispatch());
1075
1076 timer.start();
1077 let second = rx
1078 .recv_timeout(StdDuration::from_secs(1))
1079 .expect("restarted timer should re-register and fire");
1080 timer.cancel();
1081
1082 assert!(second.dispatch());
1083 assert_eq!(count.get(), 2);
1084 }
1085
1086 #[cfg(not(all(feature = "simulation", madsim)))]
1087 #[rstest]
1088 fn test_live_timer_start_while_active_restarts_and_keeps_dispatching() {
1089 let (tx, rx) = mpsc::channel();
1090 let sender = Arc::new(ChannelSender { tx });
1091 let count = Rc::new(std::cell::Cell::new(0));
1092 let callback_count = count.clone();
1093 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1094 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1095 let now = get_atomic_clock_realtime().get_time_ns();
1096 let mut timer = LiveTimer::new(
1097 Ustr::from("DOUBLE_START_TIMER"),
1098 NonZeroU64::new(1_000_000).unwrap(),
1099 now,
1100 None,
1101 TimeEventCallback::RustLocal(callback),
1102 true,
1103 Some(sender),
1104 );
1105
1106 timer.start();
1107 let first = rx
1108 .recv_timeout(StdDuration::from_secs(1))
1109 .expect("first task message should arrive");
1110
1111 timer.start();
1114 let second = rx
1115 .recv_timeout(StdDuration::from_secs(1))
1116 .expect("restarted task should keep dispatching");
1117 timer.cancel();
1118
1119 assert!(first.dispatch());
1120 assert!(second.dispatch());
1121 assert_eq!(count.get(), 2);
1122 }
1123
1124 #[cfg(not(all(feature = "simulation", madsim)))]
1125 #[rstest]
1126 fn test_live_timer_stop_before_first_fire_sends_cleanup_message() {
1127 let (tx, rx) = mpsc::channel();
1128 let sender = Arc::new(ChannelSender { tx });
1129 let count = Rc::new(std::cell::Cell::new(0));
1130 let callback_count = count.clone();
1131 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1132 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1133 let callback_weak = Rc::downgrade(&callback);
1134 let now = get_atomic_clock_realtime().get_time_ns();
1135 let mut timer = LiveTimer::new(
1136 Ustr::from("CLEANUP_TIMER"),
1137 NonZeroU64::new(1_000_000).unwrap(),
1138 now,
1139 Some(now),
1140 TimeEventCallback::RustLocal(callback),
1141 false,
1142 Some(sender),
1143 );
1144
1145 timer.start();
1146 let cleanup = rx
1147 .recv_timeout(StdDuration::from_secs(1))
1148 .expect("cleanup message should arrive");
1149
1150 assert!(callback_weak.upgrade().is_some());
1151 assert!(!cleanup.dispatch());
1152 assert_eq!(count.get(), 0);
1153
1154 drop(timer);
1157 assert!(callback_weak.upgrade().is_none());
1158 }
1159
1160 #[cfg(all(feature = "simulation", madsim))]
1161 #[madsim::test]
1162 async fn test_live_timer_uses_dst_runtime() {
1163 let count = Arc::new(AtomicUsize::new(0));
1164 let sender = Arc::new(CountingSender {
1165 count: count.clone(),
1166 });
1167 let now = get_atomic_clock_realtime().get_time_ns();
1168 let mut timer = LiveTimer::new(
1169 Ustr::from("DST_TIMER"),
1170 NonZeroU64::new(1_000_000).unwrap(),
1171 now,
1172 Some(now),
1173 TimeEventCallback::from(|_| {}),
1174 true,
1175 Some(sender),
1176 );
1177
1178 timer.start();
1179 crate::live::dst::time::sleep(crate::live::dst::time::Duration::from_millis(2)).await;
1180 crate::live::dst::task::yield_now().await;
1181
1182 assert_eq!(count.load(Ordering::Relaxed), 1);
1183 assert!(timer.is_expired());
1184 }
1185
1186 #[cfg(feature = "python")]
1187 #[rstest]
1188 fn test_live_timer_with_sender_defers_python_callback_to_handler() {
1189 Python::initialize();
1190
1191 Python::attach(|py| {
1192 let py_list = PyList::empty(py);
1193 let py_append = py_list
1194 .getattr("append")
1195 .expect("append should exist")
1196 .unbind();
1197 let callback = TimeEventCallback::from(py_append);
1198 let (tx, rx) = mpsc::channel();
1199 let sender = Arc::new(ChannelSender { tx });
1200 let now = get_atomic_clock_realtime().get_time_ns();
1201
1202 let mut timer = LiveTimer::new(
1203 Ustr::from("PY_TIMER"),
1204 NonZeroU64::new(1_000_000).unwrap(),
1205 now,
1206 None,
1207 callback,
1208 true,
1209 Some(sender),
1210 );
1211
1212 timer.start();
1213 let message = rx
1214 .recv_timeout(StdDuration::from_secs(1))
1215 .expect("timer message should arrive without acquiring the GIL on the worker");
1216 timer.cancel();
1217
1218 assert_eq!(py_list.len(), 0);
1219 assert!(message.dispatch());
1220 assert_eq!(py_list.len(), 1);
1221 });
1222 }
1223
1224 #[cfg(all(feature = "python", not(all(feature = "simulation", madsim))))]
1225 #[rstest]
1226 fn test_senderless_callback_observes_current_schedule() {
1227 Python::initialize();
1228
1229 Python::attach(|py| {
1230 let schedule = Arc::new(OnceLock::<Arc<AtomicU64>>::new());
1231 let callback_schedule = schedule.clone();
1232 let (tx, rx) = mpsc::channel();
1233 let callback = pyo3::types::PyCFunction::new_closure(
1234 py,
1235 None,
1236 None,
1237 move |_args: &pyo3::Bound<'_, pyo3::types::PyTuple>,
1238 _kwargs: Option<&pyo3::Bound<'_, pyo3::types::PyDict>>|
1239 -> pyo3::PyResult<()> {
1240 let next_time_ns = callback_schedule
1241 .get()
1242 .expect("timer schedule should be available")
1243 .load(Ordering::SeqCst);
1244 tx.send(next_time_ns)
1245 .expect("observed schedule should send");
1246 Ok(())
1247 },
1248 )
1249 .expect("callback should create")
1250 .into_any()
1251 .unbind();
1252 let now = get_atomic_clock_realtime().get_time_ns();
1253 let interval_ns = 10_000_000;
1254 let mut timer = LiveTimer::new(
1255 Ustr::from("SENDERLESS_SCHEDULE"),
1256 NonZeroU64::new(interval_ns).unwrap(),
1257 now,
1258 Some(now),
1259 TimeEventCallback::from(callback),
1260 true,
1261 None,
1262 );
1263
1264 timer.start();
1265 let expected_time_ns = timer.next_time_ns().as_u64();
1266 schedule
1267 .set(timer.next_time_ns.clone())
1268 .expect("timer schedule should set once");
1269 let observed_time_ns = py
1270 .detach(move || rx.recv_timeout(StdDuration::from_secs(1)))
1271 .expect("senderless callback should observe the schedule");
1272 wait_until(
1273 || timer.next_time_ns().as_u64() == expected_time_ns + interval_ns,
1274 StdDuration::from_secs(1),
1275 );
1276 timer.cancel();
1277
1278 assert_eq!(observed_time_ns, expected_time_ns);
1279 });
1280 }
1281}