Skip to main content

nautilus_common/live/
timer.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Live timer scheduling and callback dispatch.
17//!
18//! # Scheduling
19//!
20//! Runtime waits use the time remaining until the selected UTC deadline. Fixed timers normalize
21//! an overdue initial deadline to the current time, except at an inclusive stop boundary, and use
22//! a runtime interval to deliver overdue ticks. Deadline schedules deliver a selected overdue
23//! deadline immediately with its original timestamp, then skip to the first deadline after now.
24//! They calculate each wait inside the task. Stop times are inclusive. An event whose following
25//! deadline is not representable in [`UnixNanos`] is the timer's final event.
26//!
27//! # Task lifecycle
28//!
29//! Each [`LiveTimer::start`] creates fresh public schedule and task-state atomics. Because aborting
30//! a runtime task does not join it, task retirement linearizes restart, cancellation, and drop
31//! against event reservation. Retirement either prevents the old task from dispatching, or observes
32//! what that task reserved with its event: the following schedule, or terminal exhaustion when no
33//! successor is representable. Fresh atomics keep that task from overwriting its replacement's
34//! schedule.
35//!
36//! # Callback dispatch
37//!
38//! Thread-safe callbacks cross the worker boundary as [`TimeEventMessage`] values. `RustLocal`
39//! callbacks remain in an owner-thread registry and cross the boundary only through tokens and
40//! leases. Senderless Python callbacks run inline and publish their following schedule, when one
41//! exists, after the callback returns.
42
43use 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/// A live timer for use with a `LiveClock`.
77///
78/// `LiveTimer` triggers events at specified intervals in a real-time environment,
79/// using Tokio's async runtime to handle scheduling and execution.
80///
81/// # Threading
82///
83/// The timer runs on the runtime thread that created it and dispatches events across threads as needed.
84#[derive(Debug)]
85pub struct LiveTimer {
86    /// The name of the timer.
87    pub name: Ustr,
88    /// The fixed interval or source of UTC deadlines.
89    pub interval: TimerInterval,
90    /// The start time of the timer in UNIX nanoseconds.
91    pub start_time_ns: UnixNanos,
92    /// The optional stop time of the timer in UNIX nanoseconds.
93    pub stop_time_ns: Option<UnixNanos>,
94    /// If the timer should fire immediately at start time.
95    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    /// Creates a new [`LiveTimer`] instance.
107    ///
108    /// # Panics
109    ///
110    /// Panics if:
111    /// - `name` is not a valid string.
112    /// - `fire_immediately` is false and there is no valid deadline after `start_time_ns`.
113    #[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    /// Returns the next time in UNIX nanoseconds when the timer will fire.
168    ///
169    /// Provides the scheduled time for the next event based on the current state of the timer.
170    /// An exhausted timer retains the timestamp of its final event.
171    #[must_use]
172    pub fn next_time_ns(&self) -> UnixNanos {
173        UnixNanos::from(self.next_time_ns.load(atomic::Ordering::SeqCst))
174    }
175
176    /// Returns whether the timer is expired.
177    ///
178    /// An expired timer will not trigger any further events.
179    /// A timer that has not been started is not expired.
180    #[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    /// Starts the timer.
190    ///
191    /// Time events will begin triggering at the specified intervals.
192    /// The generated events are handled by the provided callback function.
193    ///
194    /// Starting a timer whose task is still active aborts that task first
195    /// (restart semantics); a previously fired event that is already queued
196    /// still dispatches.
197    ///
198    /// An event whose following schedule would overflow [`UnixNanos`] is the timer's final event.
199    /// The timer then remains expired, and further calls return without starting a task.
200    ///
201    /// # Panics
202    ///
203    /// Panics if using a Rust callback (`Rust` or `RustLocal`) without a `TimeEventSender`.
204    #[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        // Close the old token before registering its replacement;
232        // any lease acquired before retirement remains valid.
233        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                // A cancel or a final stop-time fire closed the token; a
241                // restart needs a fresh registration or acquire() would
242                // return None on every fire.
243                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        // Get current time
261        let clock = get_atomic_clock_realtime();
262        let now_ns = clock.get_time_ns();
263
264        // Check if the timer's alert time is in the past and adjust if needed
265        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                // Never fire an event scheduled past the stop time. The event's
298                // `ts_event` is the scheduled `next_time_ns`, so the bound is
299                // enforced on the scheduled time (matching `VirtualTimer`), not on
300                // the wall-clock read used only for `ts_init`.
301                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; // Timer expired before this event
312                }
313
314                // `schedule.tick` is cancellation safe, if the cancel branch completes
315                // first then no tick has been consumed (no event was ready).
316                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                // An event at the inclusive stop boundary or without a representable successor
322                // is terminal.
323                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                // Reserve this fire with its following schedule or terminal exhaustion. A restart
328                // observes that outcome or retires the task before it can dispatch. Registered
329                // callbacks acquire their lease first so token closure cannot suppress a reserved
330                // event.
331                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; // Stop boundary reached, or no representable successor
384                }
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    /// Cancels the timer.
398    ///
399    /// The timer will not generate a final event.
400    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                // The firing section contains only atomic schedule publication
617                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    /// The callback is retained owner-side so a restarted timer (cancel or
633    /// natural expiry closed the token) can register a fresh token.
634    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, // time_event_sender
952        );
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        // The timer retains an owner-side clone for restart; only after it
1146        // drops must no registry copy remain.
1147        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        // Second start with the first task still active: the old task is
1261        // aborted and the token stays live for the new one.
1262        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        // The timer retains an owner-side clone for restart; only after it
1304        // drops must no registry copy remain.
1305        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}