1use std::sync::{Arc, Weak};
23use atomic_refcell::AtomicRefCell;
4use log::trace;
5use shadow_shim_helper_rs::emulated_time::EmulatedTime;
6use shadow_shim_helper_rs::simulation_time::SimulationTime;
78use super::host::Host;
9use crate::core::work::task::TaskRef;
10use crate::core::worker::Worker;
11use crate::utility::{Magic, ObjectCounter};
1213pub 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.
21internal: Arc<AtomicRefCell<TimerInternal>>,
22}
2324struct 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}
3233impl TimerInternal {
34fn reset(
35&mut self,
36 next_expire_time: Option<EmulatedTime>,
37 expire_interval: Option<SimulationTime>,
38 ) {
39self.min_valid_expire_id = self.next_expire_id;
40self.expiration_count = 0;
41self.next_expire_time = next_expire_time;
42self.expire_interval = expire_interval;
43 }
44}
4546impl 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.
51pub fn new<F: 'static + Fn(&Host) + Send + Sync>(on_expire: F) -> Self {
52Self {
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 }
6566/// Returns the number of timer expirations that have occurred since the last time
67 /// [`Timer::consume_expiration_count()`] was called without resetting the counter.
68pub fn expiration_count(&self) -> u64 {
69self.magic.debug_check();
70self.internal.borrow().expiration_count
71 }
7273/// 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.
75pub fn expire_interval(&self) -> Option<SimulationTime> {
76self.magic.debug_check();
77self.internal.borrow().expire_interval
78 }
7980/// 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.
82pub fn consume_expiration_count(&mut self) -> u64 {
83self.magic.debug_check();
84let mut internal = self.internal.borrow_mut();
85let e = internal.expiration_count;
86 internal.expiration_count = 0;
87 e
88 }
8990/// Returns the remaining time until the next expiration if the timer is
91 /// armed, or None otherwise.
92pub fn remaining_time(&self) -> Option<SimulationTime> {
93self.magic.debug_check();
94let t = self.internal.borrow().next_expire_time?;
95let now = Worker::current_time().unwrap();
96Some(t.saturating_duration_since(&now))
97 }
9899/// Deactivate the timer so that it does not issue `on_expire()` callback notifications.
100pub fn disarm(&mut self) {
101self.magic.debug_check();
102let mut internal = self.internal.borrow_mut();
103 internal.reset(None, None);
104 }
105106fn timer_expire(
107 internal_weak: &Weak<AtomicRefCell<TimerInternal>>,
108 host: &Host,
109 expire_id: u64,
110 ) {
111let Some(internal) = Weak::upgrade(internal_weak) else {
112trace!("Expired Timer no longer exists.");
113return;
114 };
115116let mut internal_brw = internal.borrow_mut();
117trace!(
118"timer expire check; expireID={expire_id} minValidExpireID={}",
119 internal_brw.min_valid_expire_id
120 );
121122// The timer may have been canceled/disarmed after we scheduled the callback task.
123if expire_id < internal_brw.min_valid_expire_id {
124// Cancelled.
125return;
126 }
127128let next_expire_time = internal_brw.next_expire_time.unwrap();
129if next_expire_time > Worker::current_time().unwrap() {
130// Hasn't expired yet. Check again later.
131Self::schedule_new_expire_event(&mut internal_brw, internal_weak.clone(), host);
132return;
133 }
134135// Now we know it's a valid expiration.
136internal_brw.expiration_count += 1;
137138// A timer configured with an interval continues to periodically expire.
139if let Some(interval) = internal_brw.expire_interval {
140// The interval must be positive.
141debug_assert!(interval.is_positive());
142 internal_brw.next_expire_time = Some(next_expire_time + interval);
143Self::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).
149internal_brw.next_expire_time = None;
150 }
151152// Re-borrow as an immutable reference while executing the callback.
153drop(internal_brw);
154let internal_brw = internal.borrow();
155 (internal_brw.on_expire)(host);
156 }
157158fn schedule_new_expire_event(
159 internal_ref: &mut TimerInternal,
160 internal_ptr: Weak<AtomicRefCell<TimerInternal>>,
161 host: &Host,
162 ) {
163let now = Worker::current_time().unwrap();
164165// 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)
168let since_start = now.duration_since(&EmulatedTime::SIMULATION_START);
169let early_expire_time_since_start =
170 SimulationTime::from_secs(since_start.as_secs()) + SimulationTime::SECOND * 2;
171172let time = std::cmp::min(
173 internal_ref.next_expire_time.unwrap(),
174 EmulatedTime::SIMULATION_START + early_expire_time_since_start,
175 );
176let expire_id = internal_ref.next_expire_id;
177 internal_ref.next_expire_id += 1;
178let task = TaskRef::new(move |host| Self::timer_expire(&internal_ptr, host, expire_id));
179 host.schedule_task_at_emulated_time(task, time);
180 }
181182/// 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.
191pub fn arm(
192&mut self,
193 host: &Host,
194 expire_time: EmulatedTime,
195 expire_interval: Option<SimulationTime>,
196 ) {
197self.magic.debug_check();
198debug_assert!(expire_time >= Worker::current_time().unwrap());
199200// None is a valid expire interval, but zero is not.
201if let Some(interval) = expire_interval {
202debug_assert!(interval.is_positive());
203 }
204205let mut internal = self.internal.borrow_mut();
206 internal.reset(Some(expire_time), expire_interval);
207Self::schedule_new_expire_event(&mut internal, Arc::downgrade(&self.internal), host);
208 }
209}
210211pub mod export {
212use shadow_shim_helper_rs::emulated_time::CEmulatedTime;
213use shadow_shim_helper_rs::simulation_time::CSimulationTime;
214215use super::*;
216217/// 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)]
225pub unsafe extern "C-unwind" fn timer_new(task: *const TaskRef) -> *mut Timer {
226let task = unsafe { task.as_ref() }.unwrap().clone();
227let timer = Timer::new(move |host| task.execute(host));
228 Box::into_raw(Box::new(timer))
229 }
230231/// # Safety
232 ///
233 /// `timer` must be safely dereferenceable. Consumes `timer`.
234#[unsafe(no_mangle)]
235pub unsafe extern "C-unwind" fn timer_drop(timer: *mut Timer) {
236 drop(unsafe { Box::from_raw(timer) });
237 }
238239/// # Safety
240 ///
241 /// Pointer args must be safely dereferenceable.
242#[unsafe(no_mangle)]
243 #[allow(non_snake_case)]
244pub unsafe extern "C-unwind" fn timer_arm(
245 timer: *mut Timer,
246 host: *const Host,
247 nextExpireTime: CEmulatedTime,
248 expireInterval: CSimulationTime,
249 ) {
250let timer = unsafe { timer.as_mut() }.unwrap();
251let host = unsafe { host.as_ref().unwrap() };
252let nextExpireTime = EmulatedTime::from_c_emutime(nextExpireTime).unwrap();
253let expireInterval = SimulationTime::from_c_simtime(expireInterval).unwrap();
254 timer.arm(
255 host,
256 nextExpireTime,
257 expireInterval.is_positive().then_some(expireInterval),
258 )
259 }
260261/// # Safety
262 ///
263 /// Pointer args must be safely dereferenceable.
264#[unsafe(no_mangle)]
265pub unsafe extern "C-unwind" fn timer_disarm(timer: *mut Timer) {
266let timer = unsafe { timer.as_mut() }.unwrap();
267 timer.disarm()
268 }
269}