1use alloc::vec::Vec;
33use core::cmp::Ordering;
34use core::time::Duration;
35use std::sync::mpsc::{Receiver, RecvTimeoutError, Sender, channel};
36use std::time::Instant;
37
38enum RaiseMsg<E> {
40 At(Instant, E),
42 Stop,
44}
45
46pub struct SchedulerHandle<E> {
50 tx: Sender<RaiseMsg<E>>,
51}
52
53impl<E> Clone for SchedulerHandle<E> {
54 fn clone(&self) -> Self {
55 Self {
56 tx: self.tx.clone(),
57 }
58 }
59}
60
61impl<E> SchedulerHandle<E> {
62 pub fn raise_at(&self, at: Instant, event: E) -> bool {
66 self.tx.send(RaiseMsg::At(at, event)).is_ok()
67 }
68
69 pub fn raise_in(&self, delay: Duration, event: E) -> bool {
71 self.raise_at(Instant::now() + delay, event)
72 }
73
74 pub fn raise_now(&self, event: E) -> bool {
76 self.raise_at(Instant::now(), event)
77 }
78
79 pub fn stop(&self) {
81 let _ = self.tx.send(RaiseMsg::Stop);
82 }
83}
84
85struct Entry<E> {
88 deadline: Instant,
89 seq: u64,
90 event: E,
91}
92
93impl<E> PartialEq for Entry<E> {
94 fn eq(&self, other: &Self) -> bool {
95 self.deadline == other.deadline && self.seq == other.seq
96 }
97}
98impl<E> Eq for Entry<E> {}
99impl<E> Ord for Entry<E> {
100 fn cmp(&self, other: &Self) -> Ordering {
101 other
104 .deadline
105 .cmp(&self.deadline)
106 .then_with(|| other.seq.cmp(&self.seq))
107 }
108}
109impl<E> PartialOrd for Entry<E> {
110 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
111 Some(self.cmp(other))
112 }
113}
114
115pub struct Scheduler<E> {
119 rx: Receiver<RaiseMsg<E>>,
120 heap: alloc::collections::BinaryHeap<Entry<E>>,
121 seq: u64,
122 idle_floor: Duration,
126}
127
128impl<E> Scheduler<E> {
129 #[must_use]
132 pub fn new(idle_floor: Duration) -> (Self, SchedulerHandle<E>) {
133 let (tx, rx) = channel();
134 let sched = Self {
135 rx,
136 heap: alloc::collections::BinaryHeap::new(),
137 seq: 0,
138 idle_floor,
139 };
140 (sched, SchedulerHandle { tx })
141 }
142
143 fn push(&mut self, deadline: Instant, event: E) {
144 let seq = self.seq;
145 self.seq = self.seq.wrapping_add(1);
146 self.heap.push(Entry {
147 deadline,
148 seq,
149 event,
150 });
151 }
152
153 fn drain_channel(&mut self) -> bool {
156 let mut stop = false;
157 while let Ok(msg) = self.rx.try_recv() {
158 match msg {
159 RaiseMsg::At(at, ev) => self.push(at, ev),
160 RaiseMsg::Stop => stop = true,
161 }
162 }
163 stop
164 }
165
166 fn drain_due(&mut self, now: Instant) -> Vec<E> {
168 let mut due = Vec::new();
169 while self.heap.peek().is_some_and(|t| t.deadline <= now) {
170 if let Some(entry) = self.heap.pop() {
171 due.push(entry.event);
172 }
173 }
174 due
175 }
176
177 pub fn park_due_batch(&mut self) -> (Vec<E>, bool) {
184 let stop = self.drain_channel();
185 let due = self.drain_due(Instant::now());
186 if !due.is_empty() || stop {
187 return (due, stop);
188 }
189 let timeout = match self.heap.peek() {
191 Some(top) => top.deadline.saturating_duration_since(Instant::now()),
192 None => self.idle_floor,
193 };
194 match self.rx.recv_timeout(timeout) {
195 Ok(RaiseMsg::At(at, ev)) => self.push(at, ev),
196 Ok(RaiseMsg::Stop) => return (Vec::new(), true),
197 Err(RecvTimeoutError::Timeout) => {}
198 Err(RecvTimeoutError::Disconnected) => return (Vec::new(), true),
199 }
200 let _ = self.drain_channel();
202 (self.drain_due(Instant::now()), false)
203 }
204
205 pub fn run<F: FnMut(E)>(&mut self, mut dispatch: F) {
212 loop {
213 let stop = self.drain_channel();
214 let now = Instant::now();
215 for ev in self.drain_due(now) {
216 dispatch(ev);
217 }
218 if stop {
219 let now = Instant::now();
222 for ev in self.drain_due(now) {
223 dispatch(ev);
224 }
225 return;
226 }
227 let timeout = match self.heap.peek() {
229 Some(top) => top.deadline.saturating_duration_since(Instant::now()),
230 None => self.idle_floor,
231 };
232 match self.rx.recv_timeout(timeout) {
233 Ok(RaiseMsg::At(at, ev)) => self.push(at, ev),
234 Ok(RaiseMsg::Stop) => {
235 let now = Instant::now();
236 for ev in self.drain_due(now) {
237 dispatch(ev);
238 }
239 return;
240 }
241 Err(RecvTimeoutError::Timeout) => {} Err(RecvTimeoutError::Disconnected) => return,
243 }
244 }
245 }
246}
247
248#[cfg(test)]
249#[allow(clippy::expect_used, clippy::unwrap_used)]
250mod tests {
251 use super::*;
252 use std::sync::{Arc, Mutex};
253 use std::thread;
254
255 #[derive(Debug, Clone, PartialEq, Eq)]
256 enum Ev {
257 A,
258 B,
259 C,
260 Tick(u32),
261 }
262
263 fn run_in_thread(mut sched: Scheduler<Ev>, log: Arc<Mutex<Vec<Ev>>>) -> thread::JoinHandle<()> {
264 thread::spawn(move || {
265 sched.run(|ev| log.lock().unwrap().push(ev));
266 })
267 }
268
269 #[test]
270 fn fires_in_deadline_order_not_insertion_order() {
271 let (mut sched, h) = Scheduler::<Ev>::new(Duration::from_secs(1));
272 let now = Instant::now();
273 sched_push_for_test(&mut sched, now + Duration::from_millis(60), Ev::C);
275 sched_push_for_test(&mut sched, now + Duration::from_millis(20), Ev::A);
276 sched_push_for_test(&mut sched, now + Duration::from_millis(40), Ev::B);
277 let log = Arc::new(Mutex::new(Vec::new()));
278 let jh = run_in_thread(sched, Arc::clone(&log));
279 thread::sleep(Duration::from_millis(150));
280 h.stop();
281 jh.join().unwrap();
282 assert_eq!(*log.lock().unwrap(), vec![Ev::A, Ev::B, Ev::C]);
283 }
284
285 #[test]
286 fn raise_during_park_wakes_and_fires_early() {
287 let (mut sched, h) = Scheduler::<Ev>::new(Duration::from_secs(1));
290 sched_push_for_test(&mut sched, Instant::now() + Duration::from_secs(30), Ev::C);
291 let log = Arc::new(Mutex::new(Vec::new()));
292 let jh = run_in_thread(sched, Arc::clone(&log));
293
294 thread::sleep(Duration::from_millis(20));
295 let t0 = Instant::now();
296 h.raise_in(Duration::from_millis(10), Ev::A);
297 loop {
299 if log.lock().unwrap().contains(&Ev::A) {
300 break;
301 }
302 assert!(
303 t0.elapsed() < Duration::from_secs(2),
304 "raise must wake the park"
305 );
306 thread::sleep(Duration::from_millis(2));
307 }
308 assert!(
309 t0.elapsed() < Duration::from_secs(1),
310 "fired far before the 30s entry"
311 );
312 h.stop();
313 jh.join().unwrap();
314 }
315
316 #[test]
317 fn equal_deadline_breaks_fifo_by_seq() {
318 let (mut sched, h) = Scheduler::<Ev>::new(Duration::from_secs(1));
319 let at = Instant::now() + Duration::from_millis(20);
320 sched_push_for_test(&mut sched, at, Ev::A);
321 sched_push_for_test(&mut sched, at, Ev::B);
322 sched_push_for_test(&mut sched, at, Ev::C);
323 let log = Arc::new(Mutex::new(Vec::new()));
324 let jh = run_in_thread(sched, Arc::clone(&log));
325 thread::sleep(Duration::from_millis(120));
326 h.stop();
327 jh.join().unwrap();
328 assert_eq!(*log.lock().unwrap(), vec![Ev::A, Ev::B, Ev::C]);
329 }
330
331 #[test]
332 fn periodic_rearm_from_dispatch() {
333 let (mut sched, h) = Scheduler::<Ev>::new(Duration::from_secs(1));
335 h.raise_now(Ev::Tick(0));
336 let log = Arc::new(Mutex::new(Vec::new()));
337 let h2 = h.clone();
338 let jh = thread::spawn(move || {
339 let mut n = 0u32;
340 sched.run(|ev| {
341 if let Ev::Tick(_) = ev {
342 n += 1;
343 if n < 5 {
344 h2.raise_in(Duration::from_millis(10), Ev::Tick(n));
345 }
346 }
347 log.lock().unwrap().push(ev);
348 });
349 });
350 thread::sleep(Duration::from_millis(200));
351 h.stop();
352 jh.join().unwrap();
353 }
358
359 #[test]
360 fn raise_storm_parallel_to_fires_no_loss() {
361 let (mut sched, h) = Scheduler::<u32>::new(Duration::from_millis(50));
364 let count = Arc::new(Mutex::new(0u64));
365 let c2 = Arc::clone(&count);
366 let jh = thread::spawn(move || {
367 sched.run(|_ev: u32| {
368 *c2.lock().unwrap() += 1;
369 });
370 });
371
372 const RAISERS: u32 = 8;
373 const PER: u32 = 500;
374 let mut handles = Vec::new();
375 for _ in 0..RAISERS {
376 let hc = h.clone();
377 handles.push(thread::spawn(move || {
378 for i in 0..PER {
379 hc.raise_in(Duration::from_millis((i % 10) as u64), i);
380 }
381 }));
382 }
383 for hh in handles {
384 hh.join().unwrap();
385 }
386 thread::sleep(Duration::from_millis(300));
388 h.stop();
389 jh.join().unwrap();
390 assert_eq!(*count.lock().unwrap(), u64::from(RAISERS) * u64::from(PER));
391 }
392
393 fn sched_push_for_test<E>(s: &mut Scheduler<E>, at: Instant, ev: E) {
395 s.push(at, ev);
396 }
397}