1use std::sync::{
44 Arc,
45 atomic::{self, AtomicU8, AtomicU64},
46};
47
48use nautilus_core::{
49 DurationNanos, UUID4, UnixNanos,
50 correctness::{FAILED, check_valid_string_utf8},
51 datetime::floor_to_nearest_microsecond,
52 time::get_atomic_clock_realtime,
53};
54use ustr::Ustr;
55
56use super::dst::{
57 self,
58 task::JoinHandle,
59 time::{Duration, Instant},
60};
61#[cfg(not(all(feature = "simulation", madsim)))]
62use super::runtime::get_runtime;
63use crate::{
64 runner::{
65 TimeEventCallbackLease, TimeEventCallbackToken, TimeEventMessage, TimeEventMessageFactory,
66 TimeEventSender, register_time_event_callback,
67 },
68 timer::{TimeEvent, TimeEventCallback, Timer, TimerInterval},
69};
70
71const TASK_ACTIVE: u8 = 0;
72const TASK_FIRING: u8 = 1;
73const TASK_RETIRED: u8 = 2;
74const TASK_EXHAUSTED: u8 = 3;
75
76#[derive(Debug)]
85pub struct LiveTimer {
86 pub name: Ustr,
88 pub interval: TimerInterval,
90 pub start_time_ns: UnixNanos,
92 pub stop_time_ns: Option<UnixNanos>,
94 pub fire_immediately: bool,
96 next_time_ns: Arc<AtomicU64>,
97 callback: OwnerCallback,
98 task_handle: Option<JoinHandle<()>>,
99 task_state: Option<Arc<TimerTaskState>>,
100 exhausted: bool,
101 canceled: bool,
102 sender: Option<Arc<dyn TimeEventSender>>,
103}
104
105impl LiveTimer {
106 #[must_use]
114 pub fn new(
115 name: Ustr,
116 interval: impl Into<TimerInterval>,
117 start_time_ns: UnixNanos,
118 stop_time_ns: Option<UnixNanos>,
119 callback: TimeEventCallback,
120 fire_immediately: bool,
121 sender: Option<Arc<dyn TimeEventSender>>,
122 ) -> Self {
123 check_valid_string_utf8(name, stringify!(name)).expect(FAILED);
124
125 let interval = interval.into();
126
127 let next_time_ns = if fire_immediately {
128 start_time_ns.as_u64()
129 } else {
130 interval
131 .next_time_after(start_time_ns, start_time_ns, &mut 0)
132 .expect("first timer event should be within the UnixNanos range")
133 .as_u64()
134 };
135
136 log::trace!("Creating timer '{name}'");
137
138 let owner_callback = if sender.is_some() {
139 if callback.is_local() {
140 OwnerCallback::Registered {
141 token: register_time_event_callback(callback.clone()),
142 callback,
143 }
144 } else {
145 OwnerCallback::Direct(TimeEventMessageFactory::new(&callback))
146 }
147 } else {
148 OwnerCallback::Senderless(callback)
149 };
150
151 Self {
152 name,
153 interval,
154 start_time_ns,
155 stop_time_ns,
156 fire_immediately,
157 next_time_ns: Arc::new(AtomicU64::new(next_time_ns)),
158 callback: owner_callback,
159 task_handle: None,
160 task_state: None,
161 exhausted: false,
162 canceled: false,
163 sender,
164 }
165 }
166
167 #[must_use]
172 pub fn next_time_ns(&self) -> UnixNanos {
173 UnixNanos::from(self.next_time_ns.load(atomic::Ordering::SeqCst))
174 }
175
176 #[must_use]
181 pub fn is_expired(&self) -> bool {
182 self.canceled
183 || self
184 .task_handle
185 .as_ref()
186 .is_some_and(JoinHandle::is_finished)
187 }
188
189 #[allow(unused_variables)]
205 pub fn start(&mut self) {
206 if let OwnerCallback::Senderless(callback) = &self.callback {
207 match callback {
208 #[cfg(feature = "python")]
209 TimeEventCallback::Python(_) => {}
210 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
211 panic!("timer event sender was unset for Rust callback system");
212 }
213 }
214 }
215
216 let event_name = self.name;
217 let stop_time_ns = self.stop_time_ns;
218 let interval = self.interval.clone();
219
220 let retired_task = self.retire_task();
221
222 if self.exhausted {
223 return;
224 }
225
226 let mut observed_next = retired_task.map_or_else(
227 || self.next_time_ns.load(atomic::Ordering::SeqCst),
228 |retirement| retirement.next_time_ns,
229 );
230
231 if let Some(handle) = self.task_handle.take() {
234 self.close_registered_callback();
235 handle.abort();
236 }
237
238 let worker_dispatch = match &mut self.callback {
239 OwnerCallback::Registered { token, callback } => {
240 if token.is_closed() {
244 *token = register_time_event_callback(callback.clone());
245 }
246 WorkerDispatch::Registered(token.clone())
247 }
248 OwnerCallback::Direct(factory) => WorkerDispatch::Direct(factory.clone()),
249 OwnerCallback::Senderless(callback) => match callback {
250 #[cfg(feature = "python")]
251 TimeEventCallback::Python(callback) => {
252 WorkerDispatch::SenderlessPython(callback.clone())
253 }
254 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
255 unreachable!("senderless Rust callback rejected at start")
256 }
257 },
258 };
259
260 let clock = get_atomic_clock_realtime();
262 let now_ns = clock.get_time_ns();
263
264 let now_raw = now_ns.as_u64();
266 if should_adjust_past_due_time(observed_next, now_ns, stop_time_ns, &interval) {
267 if observed_next < now_raw {
268 let original = UnixNanos::from(observed_next);
269 log::warn!(
270 "Timer '{event_name}' alert time {} was in the past, adjusted to current time for immediate fire",
271 original.to_rfc3339(),
272 );
273 }
274
275 observed_next = now_raw;
276 }
277
278 let mut next_time_ns =
279 normalize_start_time_ns(observed_next, now_ns, stop_time_ns, &interval);
280
281 let schedule_start_ns = self.start_time_ns;
282 let next_time_atomic = Arc::new(AtomicU64::new(next_time_ns.as_u64()));
283 let task_state = Arc::new(TimerTaskState::new(next_time_ns.as_u64()));
284 self.next_time_ns = next_time_atomic.clone();
285 self.task_state = Some(task_state.clone());
286
287 let sender = self.sender.clone();
288 let now_ns = clock.get_time_ns();
289 let start = Instant::now() + timer_start_delay(next_time_ns, now_ns);
290
291 let task = async move {
292 let clock = get_atomic_clock_realtime();
293
294 let mut schedule = TimerDriver::new(interval, start, schedule_start_ns);
295
296 loop {
297 if !should_fire_scheduled_time(next_time_ns, stop_time_ns) {
302 if let (Some(sender), WorkerDispatch::Registered(token)) =
303 (sender.as_ref(), &worker_dispatch)
304 && let Some(lease) = token.acquire()
305 {
306 token.close();
307 let now_ns = clock.get_time_ns();
308 let event = TimeEvent::new(event_name, UUID4::new(), next_time_ns, now_ns);
309 sender.send(TimeEventMessage::cleanup(event, lease));
310 }
311 break; }
313
314 schedule.tick(next_time_ns).await;
317 let now_ns = clock.get_time_ns();
318
319 let event = TimeEvent::new(event_name, UUID4::new(), next_time_ns, now_ns);
320
321 let following_next_time_ns = schedule.next_time_after(next_time_ns, now_ns);
324 let expires_after_fire = expires_after_scheduled_time(next_time_ns, stop_time_ns)
325 || following_next_time_ns.is_none();
326
327 let registered_lease = if let WorkerDispatch::Registered(token) = &worker_dispatch {
332 match task_state.reserve_registered_fire(
333 token,
334 following_next_time_ns.map(|time| time.as_u64()),
335 ) {
336 Some(lease) => Some(lease),
337 None => break,
338 }
339 } else {
340 if !task_state.reserve_fire(following_next_time_ns.map(|time| time.as_u64())) {
341 break;
342 }
343 None
344 };
345
346 if sender.is_some()
347 && let Some(following_next_time_ns) = following_next_time_ns
348 {
349 next_time_atomic
350 .store(following_next_time_ns.as_u64(), atomic::Ordering::SeqCst);
351 }
352
353 match (&sender, &worker_dispatch) {
354 (Some(sender), WorkerDispatch::Direct(factory)) => {
355 sender.send(factory.message(event));
356 }
357 (Some(sender), WorkerDispatch::Registered(token)) => {
358 let lease =
359 registered_lease.expect("registered callback lease was not acquired");
360
361 if expires_after_fire {
362 token.close();
363 }
364 sender.send(TimeEventMessage::registered(event, lease));
365 }
366 #[cfg(feature = "python")]
367 (None, WorkerDispatch::SenderlessPython(callback)) => callback.call(event),
368 _ => unreachable!("timer callback dispatch did not match its sender"),
369 }
370
371 if sender.is_none()
372 && let Some(following_next_time_ns) = following_next_time_ns
373 {
374 next_time_atomic
375 .store(following_next_time_ns.as_u64(), atomic::Ordering::SeqCst);
376 }
377
378 if let Some(following_next_time_ns) = following_next_time_ns {
379 next_time_ns = following_next_time_ns;
380 }
381
382 if expires_after_fire {
383 break; }
385 }
386 };
387
388 #[cfg(all(feature = "simulation", madsim))]
389 let handle = dst::task::spawn(task);
390 #[cfg(not(all(feature = "simulation", madsim)))]
391 let handle = get_runtime().spawn(task);
392
393 self.task_handle = Some(handle);
394 self.canceled = false;
395 }
396
397 pub fn cancel(&mut self) {
401 log::trace!("Cancel timer '{}'", self.name);
402
403 self.close_registered_callback();
404
405 self.retire_task();
406
407 if let Some(handle) = self.task_handle.take() {
408 handle.abort();
409 }
410 self.canceled = true;
411 }
412
413 fn close_registered_callback(&self) {
414 if let OwnerCallback::Registered { token, .. } = &self.callback {
415 token.close();
416 }
417 }
418
419 fn retire_task(&mut self) -> Option<TimerTaskRetirement> {
420 let task_state = self.task_state.take()?;
421 let retirement = task_state.retire();
422 self.exhausted |= retirement.exhausted;
423 self.next_time_ns
424 .store(retirement.next_time_ns, atomic::Ordering::SeqCst);
425 Some(retirement)
426 }
427}
428
429impl Timer for LiveTimer {
430 fn is_expired(&self) -> bool {
431 Self::is_expired(self)
432 }
433
434 fn cancel(&mut self) {
435 Self::cancel(self);
436 }
437}
438
439impl Drop for LiveTimer {
440 fn drop(&mut self) {
441 self.close_registered_callback();
442 self.retire_task();
443
444 if let Some(handle) = self.task_handle.take() {
445 handle.abort();
446 }
447 }
448}
449
450enum TimerDriver {
451 Fixed {
452 timer: dst::time::Interval,
453 interval_ns: DurationNanos,
454 },
455 Scheduled {
456 interval: TimerInterval,
457 start_time_ns: UnixNanos,
458 index: u64,
459 },
460}
461
462impl TimerDriver {
463 fn new(interval: TimerInterval, start: Instant, start_time_ns: UnixNanos) -> Self {
464 match interval {
465 TimerInterval::Fixed(interval_ns) => Self::Fixed {
466 timer: dst::time::interval_at(
467 start,
468 Duration::from(DurationNanos::new(interval_ns.get())),
469 ),
470 interval_ns: DurationNanos::new(interval_ns.get()),
471 },
472 TimerInterval::Schedule(_) => Self::Scheduled {
473 interval,
474 start_time_ns,
475 index: 0,
476 },
477 }
478 }
479
480 async fn tick(&mut self, next_time_ns: UnixNanos) {
481 match self {
482 Self::Fixed { timer, .. } => {
483 timer.tick().await;
484 }
485 Self::Scheduled { .. } => {
486 let now = get_atomic_clock_realtime().get_time_ns();
487 dst::time::sleep_until(Instant::now() + timer_start_delay(next_time_ns, now)).await;
488 }
489 }
490 }
491
492 fn next_time_after(&mut self, time_ns: UnixNanos, now_ns: UnixNanos) -> Option<UnixNanos> {
493 match self {
494 Self::Fixed { interval_ns, .. } => time_ns.checked_add(*interval_ns),
495 Self::Scheduled {
496 interval,
497 start_time_ns,
498 index,
499 } => interval.next_time_after(*start_time_ns, time_ns.max(now_ns), index),
500 }
501 }
502}
503
504fn should_fire_scheduled_time(next_time_ns: UnixNanos, stop_time_ns: Option<UnixNanos>) -> bool {
505 stop_time_ns.is_none_or(|stop_time_ns| next_time_ns <= stop_time_ns)
506}
507
508fn expires_after_scheduled_time(next_time_ns: UnixNanos, stop_time_ns: Option<UnixNanos>) -> bool {
509 stop_time_ns == Some(next_time_ns)
510}
511
512fn is_stop_boundary(next_time_ns: u64, stop_time_ns: Option<UnixNanos>) -> bool {
513 stop_time_ns == Some(UnixNanos::from(next_time_ns))
514}
515
516fn should_adjust_past_due_time(
517 observed_next: u64,
518 now_ns: UnixNanos,
519 stop_time_ns: Option<UnixNanos>,
520 interval: &TimerInterval,
521) -> bool {
522 matches!(interval, TimerInterval::Fixed(_))
523 && observed_next <= now_ns.as_u64()
524 && !is_stop_boundary(observed_next, stop_time_ns)
525}
526
527fn normalize_start_time_ns(
528 observed_next: u64,
529 now_ns: UnixNanos,
530 stop_time_ns: Option<UnixNanos>,
531 interval: &TimerInterval,
532) -> UnixNanos {
533 if matches!(interval, TimerInterval::Schedule(_))
534 || is_stop_boundary(observed_next, stop_time_ns)
535 {
536 return UnixNanos::from(observed_next);
537 }
538
539 let now_raw = now_ns.as_u64();
540 let start_time_ns = if observed_next <= now_raw {
541 now_raw
542 } else {
543 observed_next
544 };
545
546 UnixNanos::from(floor_to_nearest_microsecond(start_time_ns))
547}
548
549fn timer_start_delay(next_time_ns: UnixNanos, now_ns: UnixNanos) -> Duration {
550 Duration::from(next_time_ns.saturating_duration_since(now_ns))
551}
552
553#[derive(Debug)]
554struct TimerTaskState {
555 status: AtomicU8,
556 next_time_ns: AtomicU64,
557}
558
559#[derive(Clone, Copy, Debug, PartialEq, Eq)]
560struct TimerTaskRetirement {
561 next_time_ns: u64,
562 exhausted: bool,
563}
564
565impl TimerTaskState {
566 fn new(next_time_ns: u64) -> Self {
567 Self {
568 status: AtomicU8::new(TASK_ACTIVE),
569 next_time_ns: AtomicU64::new(next_time_ns),
570 }
571 }
572
573 fn reserve_registered_fire(
574 &self,
575 token: &TimeEventCallbackToken,
576 following_next_time_ns: Option<u64>,
577 ) -> Option<TimeEventCallbackLease> {
578 let lease = token.acquire()?;
579 self.reserve_fire(following_next_time_ns).then_some(lease)
580 }
581
582 fn reserve_fire(&self, following_next_time_ns: Option<u64>) -> bool {
583 if self
584 .status
585 .compare_exchange(
586 TASK_ACTIVE,
587 TASK_FIRING,
588 atomic::Ordering::SeqCst,
589 atomic::Ordering::SeqCst,
590 )
591 .is_err()
592 {
593 return false;
594 }
595
596 if let Some(following_next_time_ns) = following_next_time_ns {
597 self.next_time_ns
598 .store(following_next_time_ns, atomic::Ordering::SeqCst);
599 self.status.store(TASK_ACTIVE, atomic::Ordering::SeqCst);
600 } else {
601 self.status.store(TASK_EXHAUSTED, atomic::Ordering::SeqCst);
602 }
603 true
604 }
605
606 fn retire(&self) -> TimerTaskRetirement {
607 let exhausted = loop {
608 match self.status.compare_exchange(
609 TASK_ACTIVE,
610 TASK_RETIRED,
611 atomic::Ordering::SeqCst,
612 atomic::Ordering::SeqCst,
613 ) {
614 Ok(_) | Err(TASK_RETIRED) => break false,
615 Err(TASK_EXHAUSTED) => break true,
616 Err(TASK_FIRING) => std::hint::spin_loop(),
618 Err(status) => unreachable!("invalid timer task state {status}"),
619 }
620 };
621
622 TimerTaskRetirement {
623 next_time_ns: self.next_time_ns.load(atomic::Ordering::SeqCst),
624 exhausted,
625 }
626 }
627}
628
629#[derive(Debug)]
630enum OwnerCallback {
631 Direct(TimeEventMessageFactory),
632 Registered {
635 token: TimeEventCallbackToken,
636 callback: TimeEventCallback,
637 },
638 Senderless(TimeEventCallback),
639}
640
641#[derive(Clone, Debug)]
642enum WorkerDispatch {
643 Direct(TimeEventMessageFactory),
644 Registered(TimeEventCallbackToken),
645 #[cfg(feature = "python")]
646 SenderlessPython(Arc<crate::timer::PythonTimeEventCallback>),
647}
648
649#[cfg(test)]
650mod tests {
651 #[cfg(not(all(feature = "simulation", madsim)))]
652 use std::rc::Rc;
653 #[cfg(all(feature = "python", not(all(feature = "simulation", madsim))))]
654 use std::sync::{OnceLock, atomic::AtomicU64};
655 use std::{
656 num::NonZeroU64,
657 sync::{
658 Arc,
659 atomic::{AtomicUsize, Ordering},
660 },
661 };
662 #[cfg(any(feature = "python", not(all(feature = "simulation", madsim))))]
663 use std::{sync::mpsc, time::Duration as StdDuration};
664
665 #[cfg(not(all(feature = "simulation", madsim)))]
666 use jiff::{Span, tz::TimeZone};
667 #[cfg(not(all(feature = "simulation", madsim)))]
668 use nautilus_core::{
669 DurationNanos,
670 datetime::{NANOSECONDS_IN_MILLISECOND, floor_to_nearest_microsecond},
671 };
672 use nautilus_core::{UnixNanos, time::get_atomic_clock_realtime};
673 #[cfg(not(all(feature = "simulation", madsim)))]
674 use parking_lot::Mutex;
675 #[cfg(feature = "python")]
676 use pyo3::{
677 Python,
678 types::{PyAnyMethods, PyList, PyListMethods},
679 };
680 use rstest::*;
681 use ustr::Ustr;
682
683 use super::{LiveTimer, TimerDriver};
684 #[cfg(not(all(feature = "simulation", madsim)))]
685 use crate::calendar::CalendarSchedule;
686 #[cfg(not(all(feature = "simulation", madsim)))]
687 use crate::runner::register_time_event_callback;
688 #[cfg(not(all(feature = "simulation", madsim)))]
689 use crate::testing::wait_until;
690 use crate::{
691 runner::{TimeEventMessage, TimeEventSender},
692 timer::{TimeEventCallback, TimerInterval, TimerSchedule},
693 };
694
695 #[derive(Debug)]
696 struct DeadlineSchedule;
697
698 impl TimerSchedule for DeadlineSchedule {
699 fn start_time_ns(&self) -> UnixNanos {
700 UnixNanos::from(100)
701 }
702
703 fn next_after(&self, time_ns: UnixNanos, index: &mut u64) -> Option<UnixNanos> {
704 *index = (time_ns.as_u64() - 100) / 10 + 1;
705 Some(UnixNanos::from(100 + *index * 10))
706 }
707 }
708
709 #[rstest]
710 fn test_live_timer_schedule_skips_deadlines_missed_since_event() {
711 let mut driver = TimerDriver::Scheduled {
712 interval: TimerInterval::Schedule(Arc::new(DeadlineSchedule)),
713 start_time_ns: UnixNanos::from(100),
714 index: 1,
715 };
716
717 assert_eq!(
718 driver.next_time_after(UnixNanos::from(110), UnixNanos::from(137)),
719 Some(UnixNanos::from(140))
720 );
721 assert_eq!(
722 driver.next_time_after(UnixNanos::from(140), UnixNanos::from(145)),
723 Some(UnixNanos::from(150))
724 );
725 }
726
727 #[cfg(any(feature = "python", not(all(feature = "simulation", madsim))))]
728 #[derive(Debug)]
729 struct ChannelSender {
730 tx: mpsc::Sender<TimeEventMessage>,
731 }
732
733 #[cfg(any(feature = "python", not(all(feature = "simulation", madsim))))]
734 impl TimeEventSender for ChannelSender {
735 fn send(&self, message: TimeEventMessage) {
736 self.tx.send(message).expect("message should send");
737 }
738 }
739
740 #[cfg(not(all(feature = "simulation", madsim)))]
741 #[derive(Debug)]
742 struct PausingChannelSender {
743 tx: mpsc::Sender<TimeEventMessage>,
744 release_rx: Mutex<mpsc::Receiver<()>>,
745 }
746
747 #[cfg(not(all(feature = "simulation", madsim)))]
748 impl TimeEventSender for PausingChannelSender {
749 fn send(&self, message: TimeEventMessage) {
750 self.tx.send(message).expect("message should send");
751 self.release_rx
752 .lock()
753 .recv()
754 .expect("timer send should release");
755 }
756 }
757
758 #[cfg(all(feature = "simulation", madsim))]
759 #[derive(Debug)]
760 struct CountingSender {
761 count: Arc<AtomicUsize>,
762 }
763
764 #[cfg(all(feature = "simulation", madsim))]
765 impl TimeEventSender for CountingSender {
766 fn send(&self, _message: TimeEventMessage) {
767 self.count.fetch_add(1, Ordering::Relaxed);
768 }
769 }
770
771 #[rstest]
772 #[case::unbounded(100, None, true, false)]
773 #[case::past_stop(110, Some(100), false, false)]
774 #[case::before_stop(90, Some(100), true, false)]
775 #[case::at_stop(100, Some(100), true, true)]
776 fn test_live_timer_stop_bound(
777 #[case] next_time_ns: u64,
778 #[case] stop_time_ns: Option<u64>,
779 #[case] should_fire: bool,
780 #[case] expires: bool,
781 ) {
782 let next_time_ns = UnixNanos::from(next_time_ns);
783 let stop_time_ns = stop_time_ns.map(UnixNanos::from);
784
785 assert_eq!(
786 super::should_fire_scheduled_time(next_time_ns, stop_time_ns),
787 should_fire
788 );
789 assert_eq!(
790 super::expires_after_scheduled_time(next_time_ns, stop_time_ns),
791 expires
792 );
793 }
794
795 #[rstest]
796 #[case::fixed_stop_boundary(100, 110, 100, false, false)]
797 #[case::fixed_before_stop(90, 110, 120, false, true)]
798 #[case::scheduled_stop_boundary(100, 110, 100, true, false)]
799 #[case::scheduled_before_stop(90, 110, 120, true, false)]
800 fn test_live_timer_past_due_adjustment(
801 #[case] observed_next: u64,
802 #[case] now: u64,
803 #[case] stop_time_ns: u64,
804 #[case] scheduled: bool,
805 #[case] expected: bool,
806 ) {
807 let interval = if scheduled {
808 TimerInterval::Schedule(Arc::new(DeadlineSchedule))
809 } else {
810 TimerInterval::Fixed(NonZeroU64::MIN)
811 };
812
813 assert_eq!(
814 super::should_adjust_past_due_time(
815 observed_next,
816 UnixNanos::from(now),
817 Some(UnixNanos::from(stop_time_ns)),
818 &interval,
819 ),
820 expected
821 );
822 }
823
824 #[rstest]
825 #[case::past_due(1_234_567, 2_345_678, None, 2_345_000)]
826 #[case::future(3_456_789, 2_345_678, None, 3_456_000)]
827 #[case::stop_boundary(1_234_567, 2_345_678, Some(1_234_567), 1_234_567)]
828 fn test_live_timer_start_time_normalization(
829 #[case] observed_next: u64,
830 #[case] now: u64,
831 #[case] stop_time_ns: Option<u64>,
832 #[case] expected: u64,
833 ) {
834 assert_eq!(
835 super::normalize_start_time_ns(
836 observed_next,
837 UnixNanos::from(now),
838 stop_time_ns.map(UnixNanos::from),
839 &TimerInterval::Fixed(NonZeroU64::MIN),
840 ),
841 UnixNanos::from(expected)
842 );
843 }
844
845 #[rstest]
846 #[case::full(12_000_000, 10_000_000, 2_000_000)]
847 #[case::sub_millisecond(10_500_000, 10_000_000, 500_000)]
848 fn test_live_timer_start_delay(
849 #[case] next_time_ns: u64,
850 #[case] now: u64,
851 #[case] expected_ns: u64,
852 ) {
853 assert_eq!(
854 super::timer_start_delay(UnixNanos::from(next_time_ns), UnixNanos::from(now)),
855 tokio::time::Duration::from_nanos(expected_ns)
856 );
857 }
858
859 #[rstest]
860 #[case::following_schedule(Some(200))]
861 #[case::exhaustion(None)]
862 fn test_timer_task_retirement_prevents_a_late_fire(
863 #[case] following_next_time_ns: Option<u64>,
864 ) {
865 let state = super::TimerTaskState::new(100);
866
867 let retirement = state.retire();
868 let reserved = state.reserve_fire(following_next_time_ns);
869
870 assert_eq!(retirement.next_time_ns, 100);
871 assert!(!retirement.exhausted);
872 assert!(!reserved);
873 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 100);
874 }
875
876 #[rstest]
877 fn test_timer_task_retirement_preserves_a_reserved_fire() {
878 let state = super::TimerTaskState::new(100);
879
880 let reserved = state.reserve_fire(Some(200));
881 let retirement = state.retire();
882
883 assert!(reserved);
884 assert_eq!(retirement.next_time_ns, 200);
885 assert!(!retirement.exhausted);
886 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 200);
887 }
888
889 #[rstest]
890 fn test_timer_task_retirement_preserves_exhaustion() {
891 let state = super::TimerTaskState::new(100);
892
893 let reserved = state.reserve_fire(None);
894 let retirement = state.retire();
895
896 assert!(reserved);
897 assert_eq!(retirement.next_time_ns, 100);
898 assert!(retirement.exhausted);
899 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 100);
900 }
901
902 #[cfg(not(all(feature = "simulation", madsim)))]
903 #[rstest]
904 fn test_closed_registered_callback_does_not_reserve_fire() {
905 let state = super::TimerTaskState::new(100);
906 let callback = TimeEventCallback::RustLocal(Rc::new(|_| {}));
907 let token = register_time_event_callback(callback);
908 token.close();
909
910 let lease = state.reserve_registered_fire(&token, Some(200));
911 let retirement = state.retire();
912
913 assert!(lease.is_none());
914 assert_eq!(retirement.next_time_ns, 100);
915 assert!(!retirement.exhausted);
916 assert_eq!(state.next_time_ns.load(Ordering::SeqCst), 100);
917 }
918
919 #[rstest]
920 #[case::immediate(true, 100)]
921 #[case::after_interval(false, 1_100)]
922 fn test_live_timer_fire_immediately(
923 #[case] fire_immediately: bool,
924 #[case] expected_next_time_ns: u64,
925 ) {
926 let timer = LiveTimer::new(
927 Ustr::from("TEST_TIMER"),
928 NonZeroU64::new(1000).unwrap(),
929 UnixNanos::from(100),
930 None,
931 TimeEventCallback::from(|_| {}),
932 fire_immediately,
933 None,
934 );
935
936 assert_eq!(timer.fire_immediately, fire_immediately);
937 assert_eq!(timer.next_time_ns(), UnixNanos::from(expected_next_time_ns));
938 }
939
940 #[rstest]
941 #[should_panic(expected = "timer event sender was unset for Rust callback system")]
942 fn test_live_timer_start_panics_on_senderless_rust_callback() {
943 let now = get_atomic_clock_realtime().get_time_ns();
944 let mut timer = LiveTimer::new(
945 Ustr::from("SENDERLESS_RUST"),
946 NonZeroU64::new(1_000_000).unwrap(),
947 now,
948 None,
949 TimeEventCallback::from(|_| {}),
950 false,
951 None, );
953
954 timer.start();
955 }
956
957 #[cfg(not(all(feature = "simulation", madsim)))]
958 #[rstest]
959 fn test_live_timer_uses_global_runtime() {
960 let (tx, rx) = mpsc::channel();
961 let sender = Arc::new(ChannelSender { tx });
962 let now = get_atomic_clock_realtime().get_time_ns();
963 let mut timer = LiveTimer::new(
964 Ustr::from("LIVE_TIMER"),
965 NonZeroU64::new(1_000_000).unwrap(),
966 now,
967 Some(now),
968 TimeEventCallback::from(|_| {}),
969 true,
970 Some(sender),
971 );
972
973 timer.start();
974 let message = rx
975 .recv_timeout(StdDuration::from_secs(1))
976 .expect("timer message should arrive on the global runtime");
977 wait_until(|| timer.is_expired(), StdDuration::from_secs(1));
978
979 assert_eq!(message.event().ts_event, now);
980 assert!(timer.is_expired());
981 }
982
983 #[cfg(not(all(feature = "simulation", madsim)))]
984 #[rstest]
985 fn test_live_timer_expires_after_terminal_event() {
986 let (tx, rx) = mpsc::channel();
987 let sender = Arc::new(ChannelSender { tx });
988 let now = get_atomic_clock_realtime().get_time_ns();
989 let mut timer = LiveTimer::new(
990 Ustr::from("TERMINAL_TIMER"),
991 NonZeroU64::new(u64::MAX).unwrap(),
992 now,
993 None,
994 TimeEventCallback::from(|_| {}),
995 true,
996 Some(sender),
997 );
998
999 timer.start();
1000 let scheduled_time = timer.next_time_ns();
1001 let message = rx
1002 .recv_timeout(StdDuration::from_secs(1))
1003 .expect("terminal timer event should arrive");
1004 wait_until(|| timer.is_expired(), StdDuration::from_secs(1));
1005
1006 assert_eq!(message.event().ts_event, scheduled_time);
1007 assert_eq!(timer.next_time_ns(), scheduled_time);
1008 assert!(timer.is_expired());
1009
1010 timer.start();
1011 assert!(rx.recv_timeout(StdDuration::from_millis(10)).is_err());
1012 assert_eq!(timer.next_time_ns(), scheduled_time);
1013 assert!(timer.is_expired());
1014 }
1015
1016 #[cfg(not(all(feature = "simulation", madsim)))]
1017 #[rstest]
1018 fn test_live_timer_registered_callback_expires_after_terminal_event() {
1019 let (tx, rx) = mpsc::channel();
1020 let sender = Arc::new(ChannelSender { tx });
1021 let count = Rc::new(std::cell::Cell::new(0));
1022 let callback_count = count.clone();
1023 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1024 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1025 let callback_weak = Rc::downgrade(&callback);
1026 let now = get_atomic_clock_realtime().get_time_ns();
1027 let mut timer = LiveTimer::new(
1028 Ustr::from("TERMINAL_LOCAL_TIMER"),
1029 NonZeroU64::new(u64::MAX).unwrap(),
1030 now,
1031 None,
1032 TimeEventCallback::RustLocal(callback),
1033 true,
1034 Some(sender),
1035 );
1036
1037 timer.start();
1038 let scheduled_time = timer.next_time_ns();
1039 let message = rx
1040 .recv_timeout(StdDuration::from_secs(1))
1041 .expect("registered terminal timer event should arrive");
1042 wait_until(|| timer.is_expired(), StdDuration::from_secs(1));
1043
1044 assert_eq!(message.event().ts_event, scheduled_time);
1045 assert!(message.dispatch());
1046 assert_eq!(count.get(), 1);
1047 assert_eq!(timer.next_time_ns(), scheduled_time);
1048 assert!(timer.is_expired());
1049
1050 timer.start();
1051 let token_closed = match &timer.callback {
1052 super::OwnerCallback::Registered { token, .. } => token.is_closed(),
1053 _ => false,
1054 };
1055 assert!(rx.recv_timeout(StdDuration::from_millis(10)).is_err());
1056 assert!(token_closed);
1057 assert_eq!(timer.next_time_ns(), scheduled_time);
1058 assert!(timer.is_expired());
1059 assert!(callback_weak.upgrade().is_some());
1060
1061 drop(timer);
1062 assert!(callback_weak.upgrade().is_none());
1063 }
1064
1065 #[cfg(not(all(feature = "simulation", madsim)))]
1066 #[rstest]
1067 fn test_live_timer_dispatches_rust_local_callback_on_owner_thread() {
1068 let (tx, rx) = mpsc::channel();
1069 let sender = Arc::new(ChannelSender { tx });
1070 let count = Rc::new(std::cell::Cell::new(0));
1071 let callback_count = count.clone();
1072 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1073 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1074 let now = get_atomic_clock_realtime().get_time_ns();
1075 let mut timer = LiveTimer::new(
1076 Ustr::from("LOCAL_TIMER"),
1077 NonZeroU64::new(1_000_000).unwrap(),
1078 now,
1079 Some(now),
1080 TimeEventCallback::RustLocal(callback),
1081 true,
1082 Some(sender),
1083 );
1084
1085 timer.start();
1086 let message = rx
1087 .recv_timeout(StdDuration::from_secs(1))
1088 .expect("registered timer message should arrive");
1089
1090 assert!(message.dispatch());
1091 assert_eq!(count.get(), 1);
1092 }
1093
1094 #[cfg(not(all(feature = "simulation", madsim)))]
1095 #[rstest]
1096 #[case::fixed(false)]
1097 #[case::schedule(true)]
1098 fn test_live_timer_cancel_preserves_queued_rust_local_callback_lease(#[case] scheduled: bool) {
1099 let (tx, rx) = mpsc::channel();
1100 let (release_tx, release_rx) = mpsc::channel();
1101 let sender = Arc::new(PausingChannelSender {
1102 tx,
1103 release_rx: Mutex::new(release_rx),
1104 });
1105 let count = Rc::new(std::cell::Cell::new(0));
1106 let callback_count = count.clone();
1107 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1108 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1109 let callback_weak = Rc::downgrade(&callback);
1110 let now = get_atomic_clock_realtime().get_time_ns();
1111
1112 let interval = if scheduled {
1113 TimerInterval::Schedule(Arc::new(
1114 CalendarSchedule::new(
1115 &now.to_datetime_utc().to_zoned(TimeZone::UTC),
1116 Span::new().milliseconds(1),
1117 )
1118 .unwrap(),
1119 ))
1120 } else {
1121 TimerInterval::Fixed(NonZeroU64::new(1_000_000).unwrap())
1122 };
1123
1124 let mut timer = LiveTimer::new(
1125 Ustr::from("CANCEL_QUEUED"),
1126 interval,
1127 now,
1128 None,
1129 TimeEventCallback::RustLocal(callback),
1130 true,
1131 Some(sender),
1132 );
1133
1134 timer.start();
1135 let message = rx
1136 .recv_timeout(StdDuration::from_secs(1))
1137 .expect("registered timer message should arrive");
1138 timer.cancel();
1139 release_tx.send(()).expect("timer send should release");
1140
1141 assert!(callback_weak.upgrade().is_some());
1142 assert!(message.dispatch());
1143 assert_eq!(count.get(), 1);
1144
1145 drop(timer);
1148 assert!(callback_weak.upgrade().is_none());
1149 }
1150
1151 #[cfg(not(all(feature = "simulation", madsim)))]
1152 #[rstest]
1153 fn test_live_timer_cancel_preserves_queued_direct_callback() {
1154 let (tx, rx) = mpsc::channel();
1155 let sender = Arc::new(ChannelSender { tx });
1156 let count = Arc::new(AtomicUsize::new(0));
1157 let callback_count = count.clone();
1158 let now = get_atomic_clock_realtime().get_time_ns();
1159 let mut timer = LiveTimer::new(
1160 Ustr::from("CANCEL_QUEUED_DIRECT"),
1161 NonZeroU64::new(1_000_000).unwrap(),
1162 now,
1163 None,
1164 TimeEventCallback::from(move |_| {
1165 callback_count.fetch_add(1, Ordering::Relaxed);
1166 }),
1167 true,
1168 Some(sender),
1169 );
1170
1171 timer.start();
1172 let message = rx
1173 .recv_timeout(StdDuration::from_secs(1))
1174 .expect("direct timer message should arrive");
1175 timer.cancel();
1176
1177 assert!(message.dispatch());
1178 assert_eq!(count.load(Ordering::Relaxed), 1);
1179 }
1180
1181 #[cfg(not(all(feature = "simulation", madsim)))]
1182 #[rstest]
1183 #[case::fixed(false)]
1184 #[case::schedule(true)]
1185 fn test_live_timer_restart_after_cancel_re_registers_rust_local_callback(
1186 #[case] scheduled: bool,
1187 ) {
1188 let (tx, rx) = mpsc::channel();
1189 let sender = Arc::new(ChannelSender { tx });
1190 let count = Rc::new(std::cell::Cell::new(0));
1191 let callback_count = count.clone();
1192 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1193 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1194 let now = get_atomic_clock_realtime().get_time_ns();
1195
1196 let interval = if scheduled {
1197 TimerInterval::Schedule(Arc::new(
1198 CalendarSchedule::new(
1199 &now.to_datetime_utc().to_zoned(TimeZone::UTC),
1200 Span::new().milliseconds(1),
1201 )
1202 .unwrap(),
1203 ))
1204 } else {
1205 TimerInterval::Fixed(NonZeroU64::new(1_000_000).unwrap())
1206 };
1207
1208 let mut timer = LiveTimer::new(
1209 Ustr::from("RESTART_TIMER"),
1210 interval,
1211 now,
1212 None,
1213 TimeEventCallback::RustLocal(callback),
1214 true,
1215 Some(sender),
1216 );
1217
1218 timer.start();
1219 let first = rx
1220 .recv_timeout(StdDuration::from_secs(1))
1221 .expect("first registered timer message should arrive");
1222 timer.cancel();
1223 assert!(first.dispatch());
1224
1225 timer.start();
1226 let second = rx
1227 .recv_timeout(StdDuration::from_secs(1))
1228 .expect("restarted timer should re-register and fire");
1229 timer.cancel();
1230
1231 assert!(second.dispatch());
1232 assert_eq!(count.get(), 2);
1233 }
1234
1235 #[cfg(not(all(feature = "simulation", madsim)))]
1236 #[rstest]
1237 fn test_live_timer_start_while_active_restarts_and_keeps_dispatching() {
1238 let (tx, rx) = mpsc::channel();
1239 let sender = Arc::new(ChannelSender { tx });
1240 let count = Rc::new(std::cell::Cell::new(0));
1241 let callback_count = count.clone();
1242 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1243 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1244 let now = get_atomic_clock_realtime().get_time_ns();
1245 let mut timer = LiveTimer::new(
1246 Ustr::from("DOUBLE_START_TIMER"),
1247 NonZeroU64::new(1_000_000).unwrap(),
1248 now,
1249 None,
1250 TimeEventCallback::RustLocal(callback),
1251 true,
1252 Some(sender),
1253 );
1254
1255 timer.start();
1256 let first = rx
1257 .recv_timeout(StdDuration::from_secs(1))
1258 .expect("first task message should arrive");
1259
1260 timer.start();
1263 let second = rx
1264 .recv_timeout(StdDuration::from_secs(1))
1265 .expect("restarted task should keep dispatching");
1266 timer.cancel();
1267
1268 assert!(first.dispatch());
1269 assert!(second.dispatch());
1270 assert_eq!(count.get(), 2);
1271 }
1272
1273 #[cfg(not(all(feature = "simulation", madsim)))]
1274 #[rstest]
1275 fn test_live_timer_stop_before_first_fire_sends_cleanup_message() {
1276 let (tx, rx) = mpsc::channel();
1277 let sender = Arc::new(ChannelSender { tx });
1278 let count = Rc::new(std::cell::Cell::new(0));
1279 let callback_count = count.clone();
1280 let callback: Rc<dyn Fn(crate::timer::TimeEvent)> =
1281 Rc::new(move |_| callback_count.set(callback_count.get() + 1));
1282 let callback_weak = Rc::downgrade(&callback);
1283 let now = get_atomic_clock_realtime().get_time_ns();
1284 let mut timer = LiveTimer::new(
1285 Ustr::from("CLEANUP_TIMER"),
1286 NonZeroU64::new(1_000_000).unwrap(),
1287 now,
1288 Some(now),
1289 TimeEventCallback::RustLocal(callback),
1290 false,
1291 Some(sender),
1292 );
1293
1294 timer.start();
1295 let cleanup = rx
1296 .recv_timeout(StdDuration::from_secs(1))
1297 .expect("cleanup message should arrive");
1298
1299 assert!(callback_weak.upgrade().is_some());
1300 assert!(!cleanup.dispatch());
1301 assert_eq!(count.get(), 0);
1302
1303 drop(timer);
1306 assert!(callback_weak.upgrade().is_none());
1307 }
1308
1309 #[cfg(not(all(feature = "simulation", madsim)))]
1310 #[rstest]
1311 #[case::time_only(Span::new().milliseconds(100), 100_000_000)]
1312 #[case::day(Span::new().days(1), 86_400_000_000_000)]
1313 fn test_live_timer_calendar_first_event_keeps_nanosecond_grid(
1314 #[case] span: Span,
1315 #[case] interval_ns: u64,
1316 ) {
1317 let (tx, rx) = mpsc::channel();
1318 let sender = Arc::new(ChannelSender { tx });
1319 let now = get_atomic_clock_realtime().get_time_ns();
1320 let first_time_ns = UnixNanos::from(floor_to_nearest_microsecond(now.as_u64()))
1321 + DurationNanos::new(200_000_321);
1322 let start_time_ns = first_time_ns - DurationNanos::new(interval_ns);
1323
1324 let mut timer = LiveTimer::new(
1325 Ustr::from("CALENDAR_NANOSECOND_TIMER"),
1326 TimerInterval::Schedule(Arc::new(
1327 CalendarSchedule::new(
1328 &start_time_ns.to_datetime_utc().to_zoned(TimeZone::UTC),
1329 span,
1330 )
1331 .unwrap(),
1332 )),
1333 start_time_ns,
1334 None,
1335 TimeEventCallback::from(|_| {}),
1336 false,
1337 Some(sender),
1338 );
1339
1340 timer.start();
1341 let event = rx
1342 .recv_timeout(StdDuration::from_secs(2))
1343 .expect("timer event should arrive");
1344 let next_time_ns = timer.next_time_ns();
1345 timer.cancel();
1346
1347 assert_eq!(event.event().ts_event, first_time_ns);
1348 assert_eq!(
1349 next_time_ns,
1350 first_time_ns + DurationNanos::new(interval_ns)
1351 );
1352 }
1353
1354 #[cfg(not(all(feature = "simulation", madsim)))]
1355 #[rstest]
1356 #[case::one_missed(250, false, 3, vec![1, 2, 3])]
1357 #[case::several_missed(650, false, 5, vec![1, 4, 5])]
1358 #[case::past_stop_window(650, false, 3, vec![1])]
1359 #[case::immediate_now(0, true, 1, vec![0, 1])]
1360 fn test_live_timer_calendar_interval_preserves_overdue_deadline(
1361 #[case] lateness_ms: u64,
1362 #[case] fire_immediately: bool,
1363 #[case] stop_index: u64,
1364 #[case] expected_indices: Vec<u64>,
1365 ) {
1366 let (tx, rx) = mpsc::channel();
1367 let sender = Arc::new(ChannelSender { tx });
1368 let interval_ns = DurationNanos::new(200 * NANOSECONDS_IN_MILLISECOND);
1369 let now = get_atomic_clock_realtime().get_time_ns();
1370 let start_time_ns = UnixNanos::from(floor_to_nearest_microsecond(now.as_u64()))
1371 - DurationNanos::new(lateness_ms * NANOSECONDS_IN_MILLISECOND);
1372
1373 let mut timer = LiveTimer::new(
1374 Ustr::from("CALENDAR_TIMER"),
1375 TimerInterval::Schedule(Arc::new(
1376 CalendarSchedule::new(
1377 &start_time_ns.to_datetime_utc().to_zoned(TimeZone::UTC),
1378 Span::new().milliseconds(200),
1379 )
1380 .unwrap(),
1381 )),
1382 start_time_ns,
1383 Some(start_time_ns + interval_ns * stop_index),
1384 TimeEventCallback::from(|_| {}),
1385 fire_immediately,
1386 Some(sender),
1387 );
1388
1389 timer.start();
1390
1391 let events: Vec<UnixNanos> = (0..expected_indices.len())
1392 .map(|_| {
1393 rx.recv_timeout(StdDuration::from_secs(2))
1394 .expect("timer event should arrive")
1395 .event()
1396 .ts_event
1397 })
1398 .collect();
1399
1400 let expected: Vec<UnixNanos> = expected_indices
1401 .into_iter()
1402 .map(|index| start_time_ns + interval_ns * index)
1403 .collect();
1404 assert_eq!(events, expected);
1405 assert!(rx.recv_timeout(StdDuration::from_millis(300)).is_err());
1406 }
1407
1408 #[cfg(all(feature = "simulation", madsim))]
1409 #[madsim::test]
1410 async fn test_live_timer_uses_dst_runtime() {
1411 let count = Arc::new(AtomicUsize::new(0));
1412 let sender = Arc::new(CountingSender {
1413 count: count.clone(),
1414 });
1415 let now = get_atomic_clock_realtime().get_time_ns();
1416 let mut timer = LiveTimer::new(
1417 Ustr::from("DST_TIMER"),
1418 NonZeroU64::new(1_000_000).unwrap(),
1419 now,
1420 Some(now),
1421 TimeEventCallback::from(|_| {}),
1422 true,
1423 Some(sender),
1424 );
1425
1426 timer.start();
1427 crate::live::dst::time::sleep(crate::live::dst::time::Duration::from_millis(2)).await;
1428 crate::live::dst::task::yield_now().await;
1429
1430 assert_eq!(count.load(Ordering::Relaxed), 1);
1431 assert!(timer.is_expired());
1432 }
1433
1434 #[cfg(feature = "python")]
1435 #[rstest]
1436 fn test_live_timer_with_sender_defers_python_callback_to_handler() {
1437 Python::initialize();
1438
1439 Python::attach(|py| {
1440 let py_list = PyList::empty(py);
1441 let py_append = py_list
1442 .getattr("append")
1443 .expect("append should exist")
1444 .unbind();
1445 let callback = TimeEventCallback::from(py_append);
1446 let (tx, rx) = mpsc::channel();
1447 let sender = Arc::new(ChannelSender { tx });
1448 let now = get_atomic_clock_realtime().get_time_ns();
1449
1450 let mut timer = LiveTimer::new(
1451 Ustr::from("PY_TIMER"),
1452 NonZeroU64::new(1_000_000).unwrap(),
1453 now,
1454 None,
1455 callback,
1456 true,
1457 Some(sender),
1458 );
1459
1460 timer.start();
1461 let message = rx
1462 .recv_timeout(StdDuration::from_secs(1))
1463 .expect("timer message should arrive without acquiring the GIL on the worker");
1464 timer.cancel();
1465
1466 assert_eq!(py_list.len(), 0);
1467 assert!(message.dispatch());
1468 assert_eq!(py_list.len(), 1);
1469 });
1470 }
1471
1472 #[cfg(all(feature = "python", not(all(feature = "simulation", madsim))))]
1473 #[rstest]
1474 fn test_senderless_callback_observes_current_schedule() {
1475 Python::initialize();
1476
1477 Python::attach(|py| {
1478 let schedule = Arc::new(OnceLock::<Arc<AtomicU64>>::new());
1479 let callback_schedule = schedule.clone();
1480 let (tx, rx) = mpsc::channel();
1481 let callback = pyo3::types::PyCFunction::new_closure(
1482 py,
1483 None,
1484 None,
1485 move |_args: &pyo3::Bound<'_, pyo3::types::PyTuple>,
1486 _kwargs: Option<&pyo3::Bound<'_, pyo3::types::PyDict>>|
1487 -> pyo3::PyResult<()> {
1488 let next_time_ns = callback_schedule
1489 .get()
1490 .expect("timer schedule should be available")
1491 .load(Ordering::SeqCst);
1492 tx.send(next_time_ns)
1493 .expect("observed schedule should send");
1494 Ok(())
1495 },
1496 )
1497 .expect("callback should create")
1498 .into_any()
1499 .unbind();
1500 let now = get_atomic_clock_realtime().get_time_ns();
1501 let interval_ns = 10_000_000;
1502 let mut timer = LiveTimer::new(
1503 Ustr::from("SENDERLESS_SCHEDULE"),
1504 NonZeroU64::new(interval_ns).unwrap(),
1505 now,
1506 Some(now),
1507 TimeEventCallback::from(callback),
1508 true,
1509 None,
1510 );
1511
1512 timer.start();
1513 let expected_time_ns = timer.next_time_ns().as_u64();
1514 schedule
1515 .set(timer.next_time_ns.clone())
1516 .expect("timer schedule should set once");
1517 let observed_time_ns = py
1518 .detach(move || rx.recv_timeout(StdDuration::from_secs(1)))
1519 .expect("senderless callback should observe the schedule");
1520 wait_until(
1521 || timer.next_time_ns().as_u64() == expected_time_ns + interval_ns,
1522 StdDuration::from_secs(1),
1523 );
1524 timer.cancel();
1525
1526 assert_eq!(observed_time_ns, expected_time_ns);
1527 });
1528 }
1529}