shadow_rs/host/
timer.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
use std::sync::{Arc, Weak};

use atomic_refcell::AtomicRefCell;
use log::trace;
use shadow_shim_helper_rs::emulated_time::EmulatedTime;
use shadow_shim_helper_rs::simulation_time::SimulationTime;

use super::host::Host;
use crate::core::work::task::TaskRef;
use crate::core::worker::Worker;
use crate::utility::{Magic, ObjectCounter};

pub struct Timer {
    magic: Magic<Self>,
    _counter: ObjectCounter,
    // Internals in an Arc so that we can schedule tasks that refer back to it.
    // This is the only persistent strong reference - callbacks use a Weak
    // reference.  i.e. dropping the outer object will drop this field as well;
    // scheduled callbacks with weak references that can't be upgraded become
    // no-ops.
    internal: Arc<AtomicRefCell<TimerInternal>>,
}

struct TimerInternal {
    next_expire_time: Option<EmulatedTime>,
    expire_interval: Option<SimulationTime>,
    expiration_count: u64,
    next_expire_id: u64,
    min_valid_expire_id: u64,
    on_expire: Box<dyn Fn(&Host) + Send + Sync>,
}

impl TimerInternal {
    fn reset(
        &mut self,
        next_expire_time: Option<EmulatedTime>,
        expire_interval: Option<SimulationTime>,
    ) {
        self.min_valid_expire_id = self.next_expire_id;
        self.expiration_count = 0;
        self.next_expire_time = next_expire_time;
        self.expire_interval = expire_interval;
    }
}

impl Timer {
    /// Create a new Timer that directly executes `on_expire` on
    /// expiration. `on_expire` will cause a panic if it calls mutable methods
    /// of the enclosing Timer.  If it may need to call mutable methods of the
    /// Timer, it should push a new task to the scheduler to do so.
    pub fn new<F: 'static + Fn(&Host) + Send + Sync>(on_expire: F) -> Self {
        Self {
            magic: Magic::new(),
            _counter: ObjectCounter::new("Timer"),
            internal: Arc::new(AtomicRefCell::new(TimerInternal {
                next_expire_time: None,
                expire_interval: None,
                expiration_count: 0,
                next_expire_id: 0,
                min_valid_expire_id: 0,
                on_expire: Box::new(on_expire),
            })),
        }
    }

    /// Returns the number of timer expirations that have occurred since the last time
    /// [`Timer::consume_expiration_count()`] was called without resetting the counter.
    pub fn expiration_count(&self) -> u64 {
        self.magic.debug_check();
        self.internal.borrow().expiration_count
    }

    /// Returns the currently configured timer expiration interval if this timer is configured to
    /// periodically expire, or None if the timer is configured for a one-shot expiration.
    pub fn expire_interval(&self) -> Option<SimulationTime> {
        self.magic.debug_check();
        self.internal.borrow().expire_interval
    }

    /// Returns the number of timer expirations that have occurred since the last time
    /// [`Timer::consume_expiration_count()`] was called and resets the counter to zero.
    pub fn consume_expiration_count(&mut self) -> u64 {
        self.magic.debug_check();
        let mut internal = self.internal.borrow_mut();
        let e = internal.expiration_count;
        internal.expiration_count = 0;
        e
    }

    /// Returns the remaining time until the next expiration if the timer is
    /// armed, or None otherwise.
    pub fn remaining_time(&self) -> Option<SimulationTime> {
        self.magic.debug_check();
        let t = self.internal.borrow().next_expire_time?;
        let now = Worker::current_time().unwrap();
        Some(t.saturating_duration_since(&now))
    }

    /// Deactivate the timer so that it does not issue `on_expire()` callback notifications.
    pub fn disarm(&mut self) {
        self.magic.debug_check();
        let mut internal = self.internal.borrow_mut();
        internal.reset(None, None);
    }

    fn timer_expire(
        internal_weak: &Weak<AtomicRefCell<TimerInternal>>,
        host: &Host,
        expire_id: u64,
    ) {
        let Some(internal) = Weak::upgrade(internal_weak) else {
            trace!("Expired Timer no longer exists.");
            return;
        };

        let mut internal_brw = internal.borrow_mut();
        trace!(
            "timer expire check; expireID={expire_id} minValidExpireID={}",
            internal_brw.min_valid_expire_id
        );

        // The timer may have been canceled/disarmed after we scheduled the callback task.
        if expire_id < internal_brw.min_valid_expire_id {
            // Cancelled.
            return;
        }

        let next_expire_time = internal_brw.next_expire_time.unwrap();
        if next_expire_time > Worker::current_time().unwrap() {
            // Hasn't expired yet. Check again later.
            Self::schedule_new_expire_event(&mut internal_brw, internal_weak.clone(), host);
            return;
        }

        // Now we know it's a valid expiration.
        internal_brw.expiration_count += 1;

        // A timer configured with an interval continues to periodically expire.
        if let Some(interval) = internal_brw.expire_interval {
            // The interval must be positive.
            debug_assert!(interval.is_positive());
            internal_brw.next_expire_time = Some(next_expire_time + interval);
            Self::schedule_new_expire_event(&mut internal_brw, internal_weak.clone(), host);
        } else {
            // Reset next expire time to None, so that `remaining_time`
            // correctly returns `None`, instead of `Some(0)`. (i.e. `Some(0)`
            // should mean that the timer is scheduled to fire now, but the
            // event hasn't executed yet).
            internal_brw.next_expire_time = None;
        }

        // Re-borrow as an immutable reference while executing the callback.
        drop(internal_brw);
        let internal_brw = internal.borrow();
        (internal_brw.on_expire)(host);
    }

    fn schedule_new_expire_event(
        internal_ref: &mut TimerInternal,
        internal_ptr: Weak<AtomicRefCell<TimerInternal>>,
        host: &Host,
    ) {
        let now = Worker::current_time().unwrap();

        // have the timer expire between (1,2] seconds from now, but on a 1-second edge so that all
        // timer events for all hosts will expire at the same times (and therefore in the same
        // scheduling rounds, hopefully improving scheduling parallelization)
        let since_start = now.duration_since(&EmulatedTime::SIMULATION_START);
        let early_expire_time_since_start =
            SimulationTime::from_secs(since_start.as_secs()) + SimulationTime::SECOND * 2;

        let time = std::cmp::min(
            internal_ref.next_expire_time.unwrap(),
            EmulatedTime::SIMULATION_START + early_expire_time_since_start,
        );
        let expire_id = internal_ref.next_expire_id;
        internal_ref.next_expire_id += 1;
        let task = TaskRef::new(move |host| Self::timer_expire(&internal_ptr, host, expire_id));
        host.schedule_task_at_emulated_time(task, time);
    }

    /// Activate the timer so that it starts issuing `on_expire()` callback notifications.
    ///
    /// The `expire_time` instant specifies the next time that the timer will expire and issue an
    /// `on_expire()` notification callback. The `expire_interval` duration is optional: if `Some`,
    /// it configures the timer in periodic mode where it issues `on_expire()` notification
    /// callbacks every interval of time; if `None`, the timer is configured in one-shot mode and
    /// will become disarmed after the first expiration.
    ///
    /// Panics if `expire_time` is in the past or if `expire_interval` is `Some` but not positive.
    pub fn arm(
        &mut self,
        host: &Host,
        expire_time: EmulatedTime,
        expire_interval: Option<SimulationTime>,
    ) {
        self.magic.debug_check();
        debug_assert!(expire_time >= Worker::current_time().unwrap());

        // None is a valid expire interval, but zero is not.
        if let Some(interval) = expire_interval {
            debug_assert!(interval.is_positive());
        }

        let mut internal = self.internal.borrow_mut();
        internal.reset(Some(expire_time), expire_interval);
        Self::schedule_new_expire_event(&mut internal, Arc::downgrade(&self.internal), host);
    }
}

pub mod export {
    use shadow_shim_helper_rs::emulated_time::CEmulatedTime;
    use shadow_shim_helper_rs::simulation_time::CSimulationTime;

    use super::*;

    /// Create a new Timer that synchronously executes `task` on expiration.
    ///
    /// # Safety
    ///
    /// `task` must be dereferenceable, and must not call mutable methods of
    /// the enclosing `Timer`; if it needs to do so it should schedule a new
    /// task to do so.
    #[no_mangle]
    pub unsafe extern "C-unwind" fn timer_new(task: *const TaskRef) -> *mut Timer {
        let task = unsafe { task.as_ref() }.unwrap().clone();
        let timer = Timer::new(move |host| task.execute(host));
        Box::into_raw(Box::new(timer))
    }

    /// # Safety
    ///
    /// `timer` must be safely dereferenceable. Consumes `timer`.
    #[no_mangle]
    pub unsafe extern "C-unwind" fn timer_drop(timer: *mut Timer) {
        drop(unsafe { Box::from_raw(timer) });
    }

    /// # Safety
    ///
    /// Pointer args must be safely dereferenceable.
    #[no_mangle]
    #[allow(non_snake_case)]
    pub unsafe extern "C-unwind" fn timer_arm(
        timer: *mut Timer,
        host: *const Host,
        nextExpireTime: CEmulatedTime,
        expireInterval: CSimulationTime,
    ) {
        let timer = unsafe { timer.as_mut() }.unwrap();
        let host = unsafe { host.as_ref().unwrap() };
        let nextExpireTime = EmulatedTime::from_c_emutime(nextExpireTime).unwrap();
        let expireInterval = SimulationTime::from_c_simtime(expireInterval).unwrap();
        timer.arm(
            host,
            nextExpireTime,
            expireInterval.is_positive().then_some(expireInterval),
        )
    }

    /// # Safety
    ///
    /// Pointer args must be safely dereferenceable.
    #[no_mangle]
    pub unsafe extern "C-unwind" fn timer_disarm(timer: *mut Timer) {
        let timer = unsafe { timer.as_mut() }.unwrap();
        timer.disarm()
    }
}