Skip to main content

gpui/platform/
threaded_dispatcher.rs

1use std::{
2    collections::BinaryHeap,
3    sync::Arc,
4    thread,
5    time::{Duration, Instant},
6};
7
8use parking_lot::{Condvar, Mutex};
9
10use crate::{
11    PlatformDispatcher, Priority, RunnableVariant, profiler,
12    queue::{PriorityQueueReceiver, PriorityQueueSender},
13};
14
15const MIN_THREADS: usize = 2;
16
17/// A [`PlatformDispatcher`] that runs background work on real threads, for tests and benchmarks.
18///
19/// Background tasks run in parallel on a pool of worker threads, and timers fire in real time
20/// on a timer thread. Main-thread tasks wait in a queue until the creating thread runs them
21/// with [`Self::run_until_idle`], because no platform run loop does that here.
22///
23/// Use this when a test needs production concurrency. For deterministic scheduling on one
24/// thread with a virtual clock, use [`TestDispatcher`](crate::TestDispatcher) instead.
25///
26/// # Examples
27///
28/// ```
29/// use std::sync::{
30///     Arc,
31///     atomic::{AtomicUsize, Ordering},
32/// };
33///
34/// use gpui::{BackgroundExecutor, ForegroundExecutor, ThreadedDispatcher};
35///
36/// let dispatcher = Arc::new(ThreadedDispatcher::new());
37/// let background = BackgroundExecutor::new(dispatcher.clone());
38/// let foreground = ForegroundExecutor::new(dispatcher.clone());
39/// let total = Arc::new(AtomicUsize::new(0));
40///
41/// let background_total = total.clone();
42/// background
43///     .spawn(async move {
44///         background_total.fetch_add(1, Ordering::SeqCst);
45///     })
46///     .detach();
47///
48/// let main_total = total.clone();
49/// foreground
50///     .spawn(async move {
51///         main_total.fetch_add(10, Ordering::SeqCst);
52///     })
53///     .detach();
54///
55/// // Runs the main-thread task, and waits for the background task to finish.
56/// dispatcher.run_until_idle();
57/// assert_eq!(total.load(Ordering::SeqCst), 11);
58/// ```
59pub struct ThreadedDispatcher {
60    background_sender: PriorityQueueSender<RunnableVariant>,
61    main_sender: PriorityQueueSender<RunnableVariant>,
62    main_receiver: Mutex<PriorityQueueReceiver<RunnableVariant>>,
63    timers: Arc<TimerQueue>,
64    idle: Arc<IdleTracker>,
65    main_thread_id: thread::ThreadId,
66}
67
68/// Tracks how many background and timer runnables are queued or running so
69/// [`ThreadedDispatcher::run_until_idle`] knows when to stop waiting.
70#[derive(Default)]
71struct IdleTracker {
72    inflight: Mutex<usize>,
73    condvar: Condvar,
74}
75
76impl IdleTracker {
77    fn increment(&self) {
78        *self.inflight.lock() += 1;
79    }
80
81    fn decrement(&self) {
82        let mut inflight = self.inflight.lock();
83        *inflight -= 1;
84        if *inflight == 0 {
85            self.condvar.notify_all();
86        }
87    }
88
89    /// Returns a guard that decrements the in-flight count when dropped, so
90    /// the count stays correct even if the runnable being executed panics.
91    fn decrement_on_drop(&self) -> impl Drop + '_ {
92        gpui_util::defer(|| self.decrement())
93    }
94
95    /// Notifies waiters while holding the in-flight lock. `run_until_idle`
96    /// re-checks its wake conditions under this lock before waiting, so the
97    /// notification can't slip between its check and its wait and be lost.
98    fn notify_under_lock(&self) {
99        let _inflight = self.inflight.lock();
100        self.condvar.notify_all();
101    }
102}
103
104struct TimerQueue {
105    state: Mutex<TimerQueueState>,
106    condvar: Condvar,
107}
108
109struct TimerQueueState {
110    heap: BinaryHeap<TimerEntry>,
111    next_seq: u64,
112}
113
114struct TimerEntry {
115    due: Instant,
116    seq: u64,
117    runnable: RunnableVariant,
118}
119
120impl PartialEq for TimerEntry {
121    fn eq(&self, other: &Self) -> bool {
122        self.due == other.due && self.seq == other.seq
123    }
124}
125
126impl Eq for TimerEntry {}
127
128impl PartialOrd for TimerEntry {
129    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
130        Some(self.cmp(other))
131    }
132}
133
134impl Ord for TimerEntry {
135    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
136        // Reversed so that the entry with the earliest due time (breaking ties
137        // by insertion order) is at the top of the max-heap.
138        other
139            .due
140            .cmp(&self.due)
141            .then_with(|| other.seq.cmp(&self.seq))
142    }
143}
144
145impl Default for ThreadedDispatcher {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl ThreadedDispatcher {
152    /// Creates a dispatcher whose main thread is the calling thread.
153    ///
154    /// It starts one worker thread per available CPU, with at least two, and one timer thread.
155    /// The worker threads stop when the dispatcher is dropped, but the timer thread runs for the
156    /// rest of the process, so create one dispatcher and reuse it.
157    pub fn new() -> Self {
158        let (background_sender, background_receiver) = PriorityQueueReceiver::new();
159        let (main_sender, main_receiver) = PriorityQueueReceiver::new();
160        let idle = Arc::new(IdleTracker::default());
161
162        let thread_count =
163            thread::available_parallelism().map_or(MIN_THREADS, |i| i.get().max(MIN_THREADS));
164        for i in 0..thread_count {
165            let mut receiver: PriorityQueueReceiver<RunnableVariant> = background_receiver.clone();
166            let idle = idle.clone();
167            thread::Builder::new()
168                .name(format!("ThreadedDispatcherWorker-{i}"))
169                .spawn(move || {
170                    while let Ok(runnable) = receiver.pop() {
171                        let _decrement = idle.decrement_on_drop();
172                        let location = runnable.metadata().location;
173                        let spawned = runnable.metadata().spawned;
174                        profiler::update_running_task(spawned, location);
175                        runnable.run();
176                        profiler::save_task_timing();
177                    }
178                })
179                .expect("failed to spawn threaded dispatcher worker");
180        }
181        drop(background_receiver);
182
183        let timers = Arc::new(TimerQueue {
184            state: Mutex::new(TimerQueueState {
185                heap: BinaryHeap::new(),
186                next_seq: 0,
187            }),
188            condvar: Condvar::new(),
189        });
190        {
191            let timers = timers.clone();
192            let idle = idle.clone();
193            thread::Builder::new()
194                .name("ThreadedDispatcherTimer".to_owned())
195                .spawn(move || {
196                    let mut state = timers.state.lock();
197                    loop {
198                        let Some(entry) = state.heap.peek() else {
199                            timers.condvar.wait(&mut state);
200                            continue;
201                        };
202                        let due = entry.due;
203                        if due > Instant::now() {
204                            timers.condvar.wait_until(&mut state, due);
205                            continue;
206                        }
207                        let Some(entry) = state.heap.pop() else {
208                            continue;
209                        };
210                        // Count the firing timer as in-flight before releasing
211                        // the lock so it can spawn follow-up work that
212                        // `run_until_idle` will wait for. Lock order is always
213                        // timer state, then in-flight count; `run_until_idle`
214                        // never takes them in the opposite order.
215                        idle.increment();
216                        drop(state);
217
218                        {
219                            let _decrement = idle.decrement_on_drop();
220                            let location = entry.runnable.metadata().location;
221                            let spawned = entry.runnable.metadata().spawned;
222                            profiler::update_running_task(spawned, location);
223                            entry.runnable.run();
224                            profiler::save_task_timing();
225                        }
226
227                        state = timers.state.lock();
228                    }
229                })
230                .expect("failed to spawn threaded dispatcher timer");
231        }
232
233        Self {
234            background_sender,
235            main_sender,
236            main_receiver: Mutex::new(main_receiver),
237            timers,
238            idle,
239            main_thread_id: thread::current().id(),
240        }
241    }
242
243    /// Runs queued main thread tasks and waits until no background or timer
244    /// work is queued, running, or already due.
245    ///
246    /// Timers that haven't reached their due time yet are *not* waited for:
247    /// the dispatcher runs in real time and cannot skip ahead like the
248    /// `TestDispatcher`'s virtual clock, so waiting on a future timer would
249    /// block for its full real duration. Tasks sleeping on such timers are
250    /// considered idle. Must be called on the thread that created this
251    /// dispatcher.
252    pub fn run_until_idle(&self) {
253        self.run_until_idle_with(|| self.drain_main_queue());
254    }
255
256    pub(crate) fn run_until_idle_with(&self, mut run_turn: impl FnMut() -> bool) {
257        assert!(
258            self.is_main_thread(),
259            "run_until_idle must be called on the threaded dispatcher's main thread"
260        );
261        loop {
262            if run_turn() {
263                continue;
264            }
265
266            // Checked before taking the in-flight lock; the timer thread
267            // locks them in the opposite order, so nesting would deadlock.
268            if self.has_due_timer() {
269                // Poll briefly: a firing timer leaves the heap just before it
270                // registers as in-flight.
271                let mut inflight = self.idle.inflight.lock();
272                self.idle
273                    .condvar
274                    .wait_for(&mut inflight, Duration::from_millis(1));
275                continue;
276            }
277
278            let mut inflight = self.idle.inflight.lock();
279            // Re-checked under the lock that `dispatch_on_main_thread`
280            // notifies under, so the notification can't be lost.
281            if self.main_queue_has_work() {
282                continue;
283            }
284            if *inflight == 0 {
285                // Main-thread sends happen before in-flight decrements, and
286                // decrements happen under this lock, so the check above
287                // observed all completed work.
288                return;
289            }
290            // Woken when main-thread work arrives or the in-flight count
291            // reaches zero; both notify under this lock.
292            self.idle.condvar.wait(&mut inflight);
293        }
294    }
295
296    /// Drives main-thread work until `ready` returns a value.
297    ///
298    /// Unlike [`Self::run_until_idle`], this waits across temporary quiescence.
299    /// This is required when completion can arrive from an external worker that
300    /// is not represented in the dispatcher's in-flight count.
301    ///
302    /// Readiness is checked before every main-thread runnable, so this returns
303    /// as soon as `ready` observes completion rather than after the queue
304    /// drains — deferred work that re-queues itself (idle sweeps, pollers)
305    /// must not extend a benchmark's measured interval past the completion it
306    /// awaits.
307    #[cfg(test)]
308    pub(crate) fn run_until<R>(&self, ready: impl FnMut() -> Option<R>) -> R {
309        self.run_until_with_frames(ready, || false)
310    }
311
312    /// Runs at most one queued main-thread task, returning whether one ran.
313    ///
314    /// Stepping tasks individually lets callers check completion between polls
315    /// even when work continually re-queues itself.
316    fn run_one_main_task(&self) -> bool {
317        assert!(
318            self.is_main_thread(),
319            "main tasks must run on the main thread"
320        );
321        let runnable = self.main_receiver.lock().try_pop();
322        match runnable {
323            Ok(Some(runnable)) => {
324                let location = runnable.metadata().location;
325                let spawned = runnable.metadata().spawned;
326                profiler::update_running_task(spawned, location);
327                runnable.run();
328                profiler::save_task_timing();
329                true
330            }
331            Ok(None) | Err(_) => false,
332        }
333    }
334
335    /// Runs up to the initial queue length of main-thread tasks.
336    ///
337    /// Returns whether any ran. This snapshots the count, not task identities:
338    /// newly dispatched higher-priority tasks can overtake the original tasks.
339    pub fn run_ready_main_tasks(&self) -> bool {
340        self.run_ready_main_tasks_with(|| true, || {})
341    }
342
343    /// Runs a count-bounded ready turn, checking `should_continue` before every poll.
344    /// Calls `after_poll` after each runnable returns, outside the queue lock.
345    pub(crate) fn run_ready_main_tasks_with(
346        &self,
347        mut should_continue: impl FnMut() -> bool,
348        mut after_poll: impl FnMut(),
349    ) -> bool {
350        assert!(
351            self.is_main_thread(),
352            "run_ready_main_tasks must be called on the threaded dispatcher's main thread"
353        );
354        let pending = self.main_receiver.lock().len();
355        let mut ran_any = false;
356        for _ in 0..pending {
357            if !should_continue() || !self.run_one_main_task() {
358                break;
359            }
360            ran_any = true;
361            after_poll();
362        }
363        ran_any
364    }
365
366    #[cfg(any(test, feature = "bench-support"))]
367    pub(crate) fn run_until_with_frames<R>(
368        &self,
369        mut ready: impl FnMut() -> Option<R>,
370        mut dispatch_frames: impl FnMut() -> bool,
371    ) -> R {
372        assert!(
373            self.is_main_thread(),
374            "main tasks must run on the main thread"
375        );
376        loop {
377            if let Some(result) = ready() {
378                return result;
379            }
380            let ran_task = self.run_one_main_task();
381            // A completion poll must not pull an unrelated animation frame
382            // into the measured operation.
383            if let Some(result) = ready() {
384                return result;
385            }
386            let dispatched_frames = dispatch_frames();
387            if ran_task || dispatched_frames {
388                continue;
389            }
390
391            let mut inflight = self.idle.inflight.lock();
392            if self.main_queue_has_work() {
393                continue;
394            }
395            self.idle.condvar.wait(&mut inflight);
396        }
397    }
398
399    /// Cancels every armed timer, and returns how many were canceled.
400    ///
401    /// Use it between workloads that share one dispatcher, so a timer from the earlier workload
402    /// can't fire during the later one. Dropping a timer drops its completion sender, which wakes
403    /// the task that was awaiting it. Call [`Self::run_until_idle`] afterwards to run the work that
404    /// the cancellation unblocks.
405    pub fn cancel_pending_timers(&self) -> usize {
406        let timers = {
407            let mut state = self.timers.state.lock();
408            let timers: Vec<_> = state.heap.drain().collect();
409            self.timers.condvar.notify_all();
410            timers
411        };
412        let canceled = timers.len();
413        drop(timers);
414        canceled
415    }
416
417    /// Returns a one-line summary of the dispatcher's idle-tracking state.
418    ///
419    /// The summary lists the number of in-flight background and timer runnables, the number of
420    /// armed timers, and whether main-thread work is queued. Use it to find out why
421    /// [`Self::run_until_idle`] doesn't return.
422    pub fn debug_state(&self) -> String {
423        let inflight = *self.idle.inflight.lock();
424        let timers = self.timers.state.lock().heap.len();
425        let main_queue_has_work = self.main_queue_has_work();
426        format!(
427            "ThreadedDispatcher {{ inflight: {inflight}, pending_timers: {timers}, \
428             main_queue_has_work: {main_queue_has_work} }}"
429        )
430    }
431
432    /// Whether no main-thread work is queued, no background or timer
433    /// runnables are queued or running, and no armed timer is due. Timers
434    /// that aren't due yet are ignored, as in [`Self::run_until_idle`].
435    #[cfg(any(test, feature = "bench-support"))]
436    pub(crate) fn is_idle(&self) -> bool {
437        !self.main_queue_has_work() && !self.has_due_timer() && *self.idle.inflight.lock() == 0
438    }
439
440    fn has_due_timer(&self) -> bool {
441        let state = self.timers.state.lock();
442        state
443            .heap
444            .peek()
445            .is_some_and(|entry| entry.due <= Instant::now())
446    }
447
448    fn main_queue_has_work(&self) -> bool {
449        !self.main_receiver.lock().is_empty()
450    }
451
452    fn drain_main_queue(&self) -> bool {
453        let mut ran_any = false;
454        loop {
455            // Lock only around the pop so runnables can re-entrantly dispatch
456            // more main-thread work through the sender while they run.
457            let runnable = self.main_receiver.lock().try_pop();
458            match runnable {
459                Ok(Some(runnable)) => {
460                    let location = runnable.metadata().location;
461                    let spawned = runnable.metadata().spawned;
462                    profiler::update_running_task(spawned, location);
463                    runnable.run();
464                    profiler::save_task_timing();
465                    ran_any = true;
466                }
467                Ok(None) | Err(_) => return ran_any,
468            }
469        }
470    }
471}
472
473impl PlatformDispatcher for ThreadedDispatcher {
474    fn is_main_thread(&self) -> bool {
475        thread::current().id() == self.main_thread_id
476    }
477
478    fn dispatch(&self, runnable: RunnableVariant, priority: Priority) {
479        self.idle.increment();
480        self.background_sender
481            .send(priority, runnable)
482            .unwrap_or_else(|_| panic!("threaded dispatcher workers are no longer running"));
483    }
484
485    fn dispatch_on_main_thread(&self, runnable: RunnableVariant, priority: Priority) {
486        if let Err(error) = self.main_sender.send(priority, runnable) {
487            // The main receiver lives as long as this dispatcher, so a failed
488            // send means we're mid-teardown. The runnable may wrap a !Send
489            // future, so forget it rather than dropping it on this thread
490            // (mirrors LinuxDispatcher).
491            std::mem::forget(error);
492            return;
493        }
494        // Wake `run_until_idle` if it's waiting for main-thread work.
495        self.idle.notify_under_lock();
496    }
497
498    fn dispatch_after(&self, duration: Duration, runnable: RunnableVariant) {
499        let mut state = self.timers.state.lock();
500        let seq = state.next_seq;
501        state.next_seq += 1;
502        state.heap.push(TimerEntry {
503            due: Instant::now() + duration,
504            seq,
505            runnable,
506        });
507        self.timers.condvar.notify_one();
508    }
509
510    fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
511        // This dispatcher does not need realtime scheduling priority; a plain
512        // thread keeps it portable.
513        thread::Builder::new()
514            .name("ThreadedDispatcherRealtime".to_owned())
515            .spawn(f)
516            .expect("failed to spawn threaded dispatcher realtime thread");
517    }
518
519    fn as_threaded(&self) -> Option<&ThreadedDispatcher> {
520        Some(self)
521    }
522}
523
524#[cfg(test)]
525mod tests {
526    use std::future::Future;
527    use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
528
529    use super::*;
530    use crate::{BackgroundExecutor, ForegroundExecutor};
531
532    #[test]
533    fn is_idle_tracks_queued_work_but_ignores_undue_timers() {
534        let dispatcher = Arc::new(ThreadedDispatcher::new());
535        let foreground = ForegroundExecutor::new(dispatcher.clone());
536        assert!(dispatcher.is_idle());
537
538        foreground.spawn(async {}).detach();
539        assert!(!dispatcher.is_idle());
540        dispatcher.run_until_idle();
541        assert!(dispatcher.is_idle());
542
543        let background = BackgroundExecutor::new(dispatcher.clone());
544        let timer = background.timer(Duration::from_secs(60));
545        // The timer future's initial poll runs on a worker thread; wait for
546        // it so only the armed, not-yet-due timer remains.
547        dispatcher.run_until_idle();
548        assert!(
549            dispatcher.is_idle(),
550            "a timer that is not due yet should not count as pending work"
551        );
552        drop(timer);
553        dispatcher.cancel_pending_timers();
554        dispatcher.run_until_idle();
555        assert!(dispatcher.is_idle());
556    }
557
558    #[test]
559    fn run_ready_main_tasks_does_not_wait_for_background_handoffs() {
560        let dispatcher = Arc::new(ThreadedDispatcher::new());
561        let background = BackgroundExecutor::new(dispatcher.clone());
562        let foreground = ForegroundExecutor::new(dispatcher.clone());
563
564        let (sender, receiver) = futures::channel::oneshot::channel();
565        background
566            .spawn(async move {
567                thread::sleep(Duration::from_millis(10));
568                sender.send(()).ok();
569            })
570            .detach();
571
572        let completed = Arc::new(AtomicBool::new(false));
573        foreground
574            .spawn({
575                let completed = completed.clone();
576                async move {
577                    receiver.await.ok();
578                    completed.store(true, Ordering::SeqCst);
579                }
580            })
581            .detach();
582
583        assert!(dispatcher.run_ready_main_tasks());
584        assert!(!completed.load(Ordering::SeqCst));
585
586        dispatcher.run_until_idle();
587        assert!(completed.load(Ordering::SeqCst));
588    }
589
590    #[test]
591    fn run_until_idle_completes_background_to_main_handoffs() {
592        let dispatcher = Arc::new(ThreadedDispatcher::new());
593        let background = BackgroundExecutor::new(dispatcher.clone());
594        let foreground = ForegroundExecutor::new(dispatcher.clone());
595
596        let (sender, receiver) = futures::channel::oneshot::channel();
597        background
598            .spawn(async move {
599                thread::sleep(Duration::from_millis(10));
600                sender.send(()).ok();
601            })
602            .detach();
603
604        let completed = Arc::new(AtomicBool::new(false));
605        foreground
606            .spawn({
607                let completed = completed.clone();
608                async move {
609                    receiver.await.ok();
610                    completed.store(true, Ordering::SeqCst);
611                }
612            })
613            .detach();
614
615        dispatcher.run_until_idle();
616        assert!(completed.load(Ordering::SeqCst));
617    }
618
619    #[test]
620    fn run_until_waits_for_untracked_external_wakes() {
621        let dispatcher = Arc::new(ThreadedDispatcher::new());
622        let foreground = ForegroundExecutor::new(dispatcher.clone());
623        let (sender, receiver) = futures::channel::oneshot::channel();
624        let sender_thread = thread::spawn(move || {
625            thread::sleep(Duration::from_millis(10));
626            sender
627                .send(())
628                .expect("foreground receiver should remain alive");
629        });
630
631        let completed = Arc::new(AtomicBool::new(false));
632        foreground
633            .spawn({
634                let completed = completed.clone();
635                async move {
636                    receiver
637                        .await
638                        .expect("external sender should deliver its wake");
639                    completed.store(true, Ordering::SeqCst);
640                }
641            })
642            .detach();
643
644        dispatcher.run_until(|| completed.load(Ordering::SeqCst).then_some(()));
645        sender_thread.join().expect("sender thread should finish");
646        assert!(completed.load(Ordering::SeqCst));
647    }
648
649    #[test]
650    fn run_until_returns_at_readiness_despite_requeuing_main_work() {
651        const REQUEUE_LIMIT: usize = 10_000;
652
653        let dispatcher = Arc::new(ThreadedDispatcher::new());
654        let foreground = ForegroundExecutor::new(dispatcher.clone());
655
656        // Mirrors main-thread work that yields and immediately re-queues
657        // itself (e.g. an idle-time sweep): the main queue never drains until
658        // such work finishes every iteration, so readiness must be observed
659        // between runnables rather than only at quiescence.
660        let iterations = Arc::new(AtomicUsize::new(0));
661        foreground
662            .spawn({
663                let iterations = iterations.clone();
664                async move {
665                    for _ in 0..REQUEUE_LIMIT {
666                        iterations.fetch_add(1, Ordering::SeqCst);
667                        yield_once().await;
668                    }
669                }
670            })
671            .detach();
672
673        let completed = Arc::new(AtomicBool::new(false));
674        foreground
675            .spawn({
676                let completed = completed.clone();
677                async move {
678                    completed.store(true, Ordering::SeqCst);
679                }
680            })
681            .detach();
682
683        dispatcher.run_until(|| completed.load(Ordering::SeqCst).then_some(()));
684        assert!(
685            iterations.load(Ordering::SeqCst) < REQUEUE_LIMIT,
686            "run_until should return at readiness instead of draining re-queued main work"
687        );
688    }
689
690    /// Completes after one re-schedule: the poll returns `Pending` and wakes
691    /// immediately, so the runnable re-enters the main queue.
692    fn yield_once() -> impl Future<Output = ()> {
693        let mut yielded = false;
694        std::future::poll_fn(move |poll_context| {
695            if yielded {
696                std::task::Poll::Ready(())
697            } else {
698                yielded = true;
699                poll_context.waker().wake_by_ref();
700                std::task::Poll::Pending
701            }
702        })
703    }
704
705    #[test]
706    fn run_ready_main_tasks_advances_requeuing_work_one_batch_per_call() {
707        const REQUEUE_LIMIT: usize = 10_000;
708
709        let dispatcher = Arc::new(ThreadedDispatcher::new());
710        let foreground = ForegroundExecutor::new(dispatcher.clone());
711
712        let iterations = Arc::new(AtomicUsize::new(0));
713        foreground
714            .spawn({
715                let iterations = iterations.clone();
716                async move {
717                    for _ in 0..REQUEUE_LIMIT {
718                        iterations.fetch_add(1, Ordering::SeqCst);
719                        yield_once().await;
720                    }
721                }
722            })
723            .detach();
724
725        assert!(dispatcher.run_ready_main_tasks());
726        assert_eq!(iterations.load(Ordering::SeqCst), 1);
727        assert!(dispatcher.run_ready_main_tasks());
728        assert_eq!(iterations.load(Ordering::SeqCst), 2);
729    }
730
731    #[test]
732    fn timers_fire_in_real_time() {
733        let dispatcher = Arc::new(ThreadedDispatcher::new());
734        let background = BackgroundExecutor::new(dispatcher);
735
736        let fired = Arc::new(AtomicBool::new(false));
737        let timer = background.timer(Duration::from_millis(10));
738        background
739            .spawn({
740                let fired = fired.clone();
741                async move {
742                    timer.await;
743                    fired.store(true, Ordering::SeqCst);
744                }
745            })
746            .detach();
747
748        let deadline = Instant::now() + Duration::from_secs(10);
749        while !fired.load(Ordering::SeqCst) && Instant::now() < deadline {
750            thread::sleep(Duration::from_millis(1));
751        }
752        assert!(fired.load(Ordering::SeqCst));
753    }
754
755    #[test]
756    fn cancel_pending_timers_wakes_waiters_without_waiting_for_deadline() {
757        let dispatcher = Arc::new(ThreadedDispatcher::new());
758        let background = BackgroundExecutor::new(dispatcher.clone());
759
760        let fired = Arc::new(AtomicBool::new(false));
761        let timer = background.timer(Duration::from_secs(10));
762        background
763            .spawn({
764                let fired = fired.clone();
765                async move {
766                    timer.await;
767                    fired.store(true, Ordering::SeqCst);
768                }
769            })
770            .detach();
771
772        dispatcher.run_until_idle();
773        assert_eq!(dispatcher.cancel_pending_timers(), 1);
774        dispatcher.run_until_idle();
775
776        assert!(fired.load(Ordering::SeqCst));
777        assert_eq!(dispatcher.cancel_pending_timers(), 0);
778    }
779}