shadow_rs/host/
timer.rs

1use std::sync::{Arc, Weak};
2
3use atomic_refcell::AtomicRefCell;
4use log::trace;
5use shadow_shim_helper_rs::emulated_time::EmulatedTime;
6use shadow_shim_helper_rs::simulation_time::SimulationTime;
7
8use super::host::Host;
9use crate::core::work::task::TaskRef;
10use crate::core::worker::Worker;
11use crate::utility::{Magic, ObjectCounter};
12
13pub struct Timer {
14    magic: Magic<Self>,
15    _counter: ObjectCounter,
16    // Internals in an Arc so that we can schedule tasks that refer back to it.
17    // This is the only persistent strong reference - callbacks use a Weak
18    // reference.  i.e. dropping the outer object will drop this field as well;
19    // scheduled callbacks with weak references that can't be upgraded become
20    // no-ops.
21    internal: Arc<AtomicRefCell<TimerInternal>>,
22}
23
24struct TimerInternal {
25    next_expire_time: Option<EmulatedTime>,
26    expire_interval: Option<SimulationTime>,
27    expiration_count: u64,
28    next_expire_id: u64,
29    min_valid_expire_id: u64,
30    on_expire: Box<dyn Fn(&Host) + Send + Sync>,
31}
32
33impl TimerInternal {
34    fn reset(
35        &mut self,
36        next_expire_time: Option<EmulatedTime>,
37        expire_interval: Option<SimulationTime>,
38    ) {
39        self.min_valid_expire_id = self.next_expire_id;
40        self.expiration_count = 0;
41        self.next_expire_time = next_expire_time;
42        self.expire_interval = expire_interval;
43    }
44}
45
46impl Timer {
47    /// Create a new Timer that directly executes `on_expire` on
48    /// expiration. `on_expire` will cause a panic if it calls mutable methods
49    /// of the enclosing Timer.  If it may need to call mutable methods of the
50    /// Timer, it should push a new task to the scheduler to do so.
51    pub fn new<F: 'static + Fn(&Host) + Send + Sync>(on_expire: F) -> Self {
52        Self {
53            magic: Magic::new(),
54            _counter: ObjectCounter::new("Timer"),
55            internal: Arc::new(AtomicRefCell::new(TimerInternal {
56                next_expire_time: None,
57                expire_interval: None,
58                expiration_count: 0,
59                next_expire_id: 0,
60                min_valid_expire_id: 0,
61                on_expire: Box::new(on_expire),
62            })),
63        }
64    }
65
66    /// Returns the number of timer expirations that have occurred since the last time
67    /// [`Timer::consume_expiration_count()`] was called without resetting the counter.
68    pub fn expiration_count(&self) -> u64 {
69        self.magic.debug_check();
70        self.internal.borrow().expiration_count
71    }
72
73    /// Returns the currently configured timer expiration interval if this timer is configured to
74    /// periodically expire, or None if the timer is configured for a one-shot expiration.
75    pub fn expire_interval(&self) -> Option<SimulationTime> {
76        self.magic.debug_check();
77        self.internal.borrow().expire_interval
78    }
79
80    /// Returns the number of timer expirations that have occurred since the last time
81    /// [`Timer::consume_expiration_count()`] was called and resets the counter to zero.
82    pub fn consume_expiration_count(&mut self) -> u64 {
83        self.magic.debug_check();
84        let mut internal = self.internal.borrow_mut();
85        let e = internal.expiration_count;
86        internal.expiration_count = 0;
87        e
88    }
89
90    /// Returns the remaining time until the next expiration if the timer is
91    /// armed, or None otherwise.
92    pub fn remaining_time(&self) -> Option<SimulationTime> {
93        self.magic.debug_check();
94        let t = self.internal.borrow().next_expire_time?;
95        let now = Worker::current_time().unwrap();
96        Some(t.saturating_duration_since(&now))
97    }
98
99    /// Deactivate the timer so that it does not issue `on_expire()` callback notifications.
100    pub fn disarm(&mut self) {
101        self.magic.debug_check();
102        let mut internal = self.internal.borrow_mut();
103        internal.reset(None, None);
104    }
105
106    fn timer_expire(
107        internal_weak: &Weak<AtomicRefCell<TimerInternal>>,
108        host: &Host,
109        expire_id: u64,
110    ) {
111        let Some(internal) = Weak::upgrade(internal_weak) else {
112            trace!("Expired Timer no longer exists.");
113            return;
114        };
115
116        let mut internal_brw = internal.borrow_mut();
117        trace!(
118            "timer expire check; expireID={expire_id} minValidExpireID={}",
119            internal_brw.min_valid_expire_id
120        );
121
122        // The timer may have been canceled/disarmed after we scheduled the callback task.
123        if expire_id < internal_brw.min_valid_expire_id {
124            // Cancelled.
125            return;
126        }
127
128        let next_expire_time = internal_brw.next_expire_time.unwrap();
129        if next_expire_time > Worker::current_time().unwrap() {
130            // Hasn't expired yet. Check again later.
131            Self::schedule_new_expire_event(&mut internal_brw, internal_weak.clone(), host);
132            return;
133        }
134
135        // Now we know it's a valid expiration.
136        internal_brw.expiration_count += 1;
137
138        // A timer configured with an interval continues to periodically expire.
139        if let Some(interval) = internal_brw.expire_interval {
140            // The interval must be positive.
141            debug_assert!(interval.is_positive());
142            internal_brw.next_expire_time = Some(next_expire_time + interval);
143            Self::schedule_new_expire_event(&mut internal_brw, internal_weak.clone(), host);
144        } else {
145            // Reset next expire time to None, so that `remaining_time`
146            // correctly returns `None`, instead of `Some(0)`. (i.e. `Some(0)`
147            // should mean that the timer is scheduled to fire now, but the
148            // event hasn't executed yet).
149            internal_brw.next_expire_time = None;
150        }
151
152        // Re-borrow as an immutable reference while executing the callback.
153        drop(internal_brw);
154        let internal_brw = internal.borrow();
155        (internal_brw.on_expire)(host);
156    }
157
158    fn schedule_new_expire_event(
159        internal_ref: &mut TimerInternal,
160        internal_ptr: Weak<AtomicRefCell<TimerInternal>>,
161        host: &Host,
162    ) {
163        let now = Worker::current_time().unwrap();
164
165        // have the timer expire between (1,2] seconds from now, but on a 1-second edge so that all
166        // timer events for all hosts will expire at the same times (and therefore in the same
167        // scheduling rounds, hopefully improving scheduling parallelization)
168        let since_start = now.duration_since(&EmulatedTime::SIMULATION_START);
169        let early_expire_time_since_start =
170            SimulationTime::from_secs(since_start.as_secs()) + SimulationTime::SECOND * 2;
171
172        let time = std::cmp::min(
173            internal_ref.next_expire_time.unwrap(),
174            EmulatedTime::SIMULATION_START + early_expire_time_since_start,
175        );
176        let expire_id = internal_ref.next_expire_id;
177        internal_ref.next_expire_id += 1;
178        let task = TaskRef::new(move |host| Self::timer_expire(&internal_ptr, host, expire_id));
179        host.schedule_task_at_emulated_time(task, time);
180    }
181
182    /// Activate the timer so that it starts issuing `on_expire()` callback notifications.
183    ///
184    /// The `expire_time` instant specifies the next time that the timer will expire and issue an
185    /// `on_expire()` notification callback. The `expire_interval` duration is optional: if `Some`,
186    /// it configures the timer in periodic mode where it issues `on_expire()` notification
187    /// callbacks every interval of time; if `None`, the timer is configured in one-shot mode and
188    /// will become disarmed after the first expiration.
189    ///
190    /// Panics if `expire_time` is in the past or if `expire_interval` is `Some` but not positive.
191    pub fn arm(
192        &mut self,
193        host: &Host,
194        expire_time: EmulatedTime,
195        expire_interval: Option<SimulationTime>,
196    ) {
197        self.magic.debug_check();
198        debug_assert!(expire_time >= Worker::current_time().unwrap());
199
200        // None is a valid expire interval, but zero is not.
201        if let Some(interval) = expire_interval {
202            debug_assert!(interval.is_positive());
203        }
204
205        let mut internal = self.internal.borrow_mut();
206        internal.reset(Some(expire_time), expire_interval);
207        Self::schedule_new_expire_event(&mut internal, Arc::downgrade(&self.internal), host);
208    }
209}
210
211pub mod export {
212    use shadow_shim_helper_rs::emulated_time::CEmulatedTime;
213    use shadow_shim_helper_rs::simulation_time::CSimulationTime;
214
215    use super::*;
216
217    /// Create a new Timer that synchronously executes `task` on expiration.
218    ///
219    /// # Safety
220    ///
221    /// `task` must be dereferenceable, and must not call mutable methods of
222    /// the enclosing `Timer`; if it needs to do so it should schedule a new
223    /// task to do so.
224    #[unsafe(no_mangle)]
225    pub unsafe extern "C-unwind" fn timer_new(task: *const TaskRef) -> *mut Timer {
226        let task = unsafe { task.as_ref() }.unwrap().clone();
227        let timer = Timer::new(move |host| task.execute(host));
228        Box::into_raw(Box::new(timer))
229    }
230
231    /// # Safety
232    ///
233    /// `timer` must be safely dereferenceable. Consumes `timer`.
234    #[unsafe(no_mangle)]
235    pub unsafe extern "C-unwind" fn timer_drop(timer: *mut Timer) {
236        drop(unsafe { Box::from_raw(timer) });
237    }
238
239    /// # Safety
240    ///
241    /// Pointer args must be safely dereferenceable.
242    #[unsafe(no_mangle)]
243    #[allow(non_snake_case)]
244    pub unsafe extern "C-unwind" fn timer_arm(
245        timer: *mut Timer,
246        host: *const Host,
247        nextExpireTime: CEmulatedTime,
248        expireInterval: CSimulationTime,
249    ) {
250        let timer = unsafe { timer.as_mut() }.unwrap();
251        let host = unsafe { host.as_ref().unwrap() };
252        let nextExpireTime = EmulatedTime::from_c_emutime(nextExpireTime).unwrap();
253        let expireInterval = SimulationTime::from_c_simtime(expireInterval).unwrap();
254        timer.arm(
255            host,
256            nextExpireTime,
257            expireInterval.is_positive().then_some(expireInterval),
258        )
259    }
260
261    /// # Safety
262    ///
263    /// Pointer args must be safely dereferenceable.
264    #[unsafe(no_mangle)]
265    pub unsafe extern "C-unwind" fn timer_disarm(timer: *mut Timer) {
266        let timer = unsafe { timer.as_mut() }.unwrap();
267        timer.disarm()
268    }
269}