1use crate::PlatformDispatcher;
2use async_task::Runnable;
3use chrono::{DateTime, Utc};
4use futures::channel::oneshot;
5use scheduler::Instant;
6use scheduler::RunnableMeta;
7use scheduler::{
8 Clock, LocalExecutor, Priority, Scheduler, SessionId, Task, TestScheduler, Timer,
9 spawn_dedicated_thread,
10};
11#[cfg(not(target_family = "wasm"))]
12use std::task::{Context, Poll};
13use std::{
14 any::Any,
15 future::Future,
16 pin::Pin,
17 sync::{
18 Arc,
19 atomic::{AtomicU16, Ordering},
20 },
21 time::Duration,
22};
23
24pub struct PlatformScheduler {
29 dispatcher: Arc<dyn PlatformDispatcher>,
30 clock: Arc<PlatformClock>,
31 next_session_id: AtomicU16,
32 #[cfg(feature = "profiler")]
33 foreground_runnables: crate::profiler::ForegroundRunnableCounter,
34}
35
36impl PlatformScheduler {
37 pub fn new(dispatcher: Arc<dyn PlatformDispatcher>) -> Self {
39 Self {
40 dispatcher: dispatcher.clone(),
41 clock: Arc::new(PlatformClock { dispatcher }),
42 next_session_id: AtomicU16::new(0),
43 #[cfg(feature = "profiler")]
44 foreground_runnables: crate::profiler::foreground_runnable_counter(),
45 }
46 }
47
48 pub fn foreground_executor(self: &Arc<Self>) -> LocalExecutor {
50 let session_id = self.next_session_id();
51 let scheduler = Arc::downgrade(self);
52 LocalExecutor::new(session_id, self.clone(), move |runnable| {
53 if let Some(scheduler) = scheduler.upgrade() {
54 scheduler.schedule_local(session_id, runnable);
55 }
56 })
57 }
58
59 fn next_session_id(&self) -> SessionId {
60 SessionId::new(self.next_session_id.fetch_add(1, Ordering::SeqCst))
61 }
62
63 #[cfg(feature = "profiler")]
64 pub(crate) fn foreground_runnable_counter(&self) -> crate::profiler::ForegroundRunnableCounter {
65 self.foreground_runnables.clone()
66 }
67}
68
69impl Scheduler for PlatformScheduler {
70 #[cfg(not(target_family = "wasm"))]
71 fn block(
72 &self,
73 _session_id: Option<SessionId>,
74 mut future: Pin<&mut dyn Future<Output = ()>>,
75 timeout: Option<Duration>,
76 ) -> bool {
77 use waker_fn::waker_fn;
78 let deadline = timeout.map(|t| Instant::now() + t);
79 let parker = parking::Parker::new();
80 let unparker = parker.unparker();
81 let waker = waker_fn(move || {
82 unparker.unpark();
83 });
84 let mut cx = Context::from_waker(&waker);
85 if let Poll::Ready(()) = future.as_mut().poll(&mut cx) {
86 return true;
87 }
88
89 let park_deadline = |deadline: Instant| {
90 let _timer_guard = self.dispatcher.increase_timer_resolution();
93 parker.park_deadline(deadline)
94 };
95
96 loop {
97 match deadline {
98 Some(deadline) if !park_deadline(deadline) && deadline <= Instant::now() => {
99 return false;
100 }
101 Some(_) => (),
102 None => parker.park(),
103 }
104 if let Poll::Ready(()) = future.as_mut().poll(&mut cx) {
105 break true;
106 }
107 }
108 }
109
110 fn schedule_local(&self, _session_id: SessionId, runnable: Runnable<RunnableMeta>) {
111 #[cfg(feature = "profiler")]
112 self.foreground_runnables.queued();
113 self.dispatcher
114 .dispatch_on_main_thread(runnable, Priority::default());
115 }
116
117 fn schedule_background_with_priority(
118 &self,
119 runnable: Runnable<RunnableMeta>,
120 priority: Priority,
121 ) {
122 self.dispatcher.dispatch(runnable, priority);
123 }
124
125 fn spawn_realtime(&self, f: Box<dyn FnOnce() + Send>) {
126 self.dispatcher.spawn_realtime(f);
127 }
128
129 #[track_caller]
130 fn timer(&self, duration: Duration) -> Timer {
131 let (tx, rx) = oneshot::channel();
132 let dispatcher = self.dispatcher.clone();
133
134 let location = std::panic::Location::caller();
136 let (runnable, _task) = async_task::Builder::new()
137 .metadata(RunnableMeta {
138 location,
139 spawned: scheduler::SpawnTime(Instant::now()),
140 })
141 .spawn(
142 move |_| async move {
143 let _ = tx.send(());
144 },
145 move |runnable| {
146 dispatcher.dispatch_after(duration, runnable);
147 },
148 );
149 runnable.schedule();
150
151 Timer::new(rx)
152 }
153
154 fn clock(&self) -> Arc<dyn Clock> {
155 self.clock.clone()
156 }
157
158 fn spawn_dedicated(
159 self: Arc<Self>,
160 f: Box<
161 dyn FnOnce(
162 LocalExecutor,
163 )
164 -> Pin<Box<dyn Future<Output = Box<dyn Any + Send + Sync>> + 'static>>
165 + Send
166 + 'static,
167 >,
168 ) -> Task<Box<dyn Any + Send + Sync>> {
169 let session_id = self.next_session_id();
170 spawn_dedicated_thread(session_id, self, move |executor| f(executor))
171 }
172
173 fn as_test(&self) -> Option<&TestScheduler> {
174 None
175 }
176}
177
178struct PlatformClock {
180 dispatcher: Arc<dyn PlatformDispatcher>,
181}
182
183impl Clock for PlatformClock {
184 fn utc_now(&self) -> DateTime<Utc> {
185 Utc::now()
186 }
187
188 fn now(&self) -> Instant {
189 self.dispatcher.now()
190 }
191}
192
193#[cfg(all(test, not(target_family = "wasm")))]
194mod tests {
195 use super::*;
196 use crate::RunnableVariant;
197 use scheduler::BackgroundExecutor;
198 use std::time::Instant as StdInstant;
199
200 struct SmokeDispatcher;
203
204 impl PlatformDispatcher for SmokeDispatcher {
205 fn is_main_thread(&self) -> bool {
206 false
207 }
208 fn dispatch(&self, _runnable: RunnableVariant, _priority: Priority) {
209 panic!("SmokeDispatcher should not be asked to dispatch in this test");
210 }
211 fn dispatch_on_main_thread(&self, _runnable: RunnableVariant, _priority: Priority) {
212 panic!("SmokeDispatcher does not implement a main thread");
213 }
214 fn dispatch_after(&self, _duration: Duration, _runnable: RunnableVariant) {
215 panic!("SmokeDispatcher does not implement timers");
216 }
217 fn spawn_realtime(&self, _f: Box<dyn FnOnce() + Send>) {
218 panic!("SmokeDispatcher does not implement realtime");
219 }
220 }
221
222 #[test]
223 fn dedicated_executor_tasks_share_one_thread() {
224 let background =
225 BackgroundExecutor::new(Arc::new(PlatformScheduler::new(Arc::new(SmokeDispatcher))));
226 let dedicated = scheduler::DedicatedExecutor::new(&background);
227
228 let first = dedicated.spawn(async { std::thread::current().id() });
229 let second = dedicated.spawn(async { std::thread::current().id() });
230
231 let first = futures::executor::block_on(first);
232 let second = futures::executor::block_on(second);
233
234 assert_eq!(first, second, "tasks must share the dedicated thread");
235 assert_ne!(
236 first,
237 std::thread::current().id(),
238 "dedicated tasks must not run on the spawning thread"
239 );
240 }
241
242 #[test]
243 fn spawn_dedicated_runs_on_a_real_separate_thread() {
244 let background =
245 BackgroundExecutor::new(Arc::new(PlatformScheduler::new(Arc::new(SmokeDispatcher))));
246 let started = StdInstant::now();
247 let task = background.spawn_dedicated(|_executor| async move {
248 let thread_id_before = std::thread::current().id();
252 std::thread::sleep(Duration::from_millis(50));
253 let thread_id_after = std::thread::current().id();
254 assert_eq!(thread_id_before, thread_id_after);
255 (thread_id_before, "slept")
256 });
257 let (dedicated_thread_id, message) = futures::executor::block_on(task);
258 let elapsed = started.elapsed();
259 assert_eq!(message, "slept");
260 assert_ne!(
261 dedicated_thread_id,
262 std::thread::current().id(),
263 "dedicated future ran on the test thread"
264 );
265 assert!(
266 elapsed >= Duration::from_millis(40),
267 "expected the dedicated thread to genuinely sleep, elapsed = {:?}",
268 elapsed
269 );
270 }
271
272 #[test]
273 fn spawn_dedicated_returns_not_send_future_output() {
274 use std::cell::RefCell;
278 use std::rc::Rc;
279
280 let background =
281 BackgroundExecutor::new(Arc::new(PlatformScheduler::new(Arc::new(SmokeDispatcher))));
282 let task = background.spawn_dedicated(|_executor| async move {
283 let state = Rc::new(RefCell::new(0_i32));
284 for _ in 0..3 {
285 *state.borrow_mut() += 1;
286 }
287 *state.borrow()
288 });
289 let output = futures::executor::block_on(task);
290 assert_eq!(output, 3);
291 }
292
293 #[test]
294 fn spawn_dedicated_dropping_task_cancels_future() {
295 use parking_lot::Mutex;
296 use std::sync::mpsc;
297
298 let background =
299 BackgroundExecutor::new(Arc::new(PlatformScheduler::new(Arc::new(SmokeDispatcher))));
300
301 let (started_tx, started_rx) = mpsc::channel::<()>();
302 let (after_park_tx, after_park_rx) = mpsc::channel::<()>();
303 let observed_post_await_write = Arc::new(Mutex::new(false));
304
305 let task = {
306 let observed_post_await_write = observed_post_await_write.clone();
307 background.spawn_dedicated(move |_executor| async move {
308 started_tx
310 .send(())
311 .expect("started signal must be received");
312 futures::future::pending::<()>().await;
315 *observed_post_await_write.lock() = true;
316 after_park_tx
317 .send(())
318 .expect("after-park signal must be received");
319 })
320 };
321
322 started_rx
324 .recv_timeout(Duration::from_secs(2))
325 .expect("dedicated future failed to start");
326
327 drop(task);
329
330 assert!(
333 after_park_rx
334 .recv_timeout(Duration::from_millis(100))
335 .is_err(),
336 "dedicated future advanced past the await after its Task was dropped"
337 );
338 assert!(
339 !*observed_post_await_write.lock(),
340 "dedicated future ran code past the cancellation point"
341 );
342 }
343
344 #[test]
345 fn spawn_dedicated_thread_tears_down_after_work_completes() {
346 use std::sync::mpsc;
347
348 struct DropSignal {
351 tx: Option<mpsc::Sender<std::thread::ThreadId>>,
352 }
353 impl Drop for DropSignal {
354 fn drop(&mut self) {
355 if let Some(tx) = self.tx.take() {
356 let _ = tx.send(std::thread::current().id());
357 }
358 }
359 }
360
361 let background =
362 BackgroundExecutor::new(Arc::new(PlatformScheduler::new(Arc::new(SmokeDispatcher))));
363 let (started_tx, started_rx) = mpsc::channel::<std::thread::ThreadId>();
364 let (drop_tx, drop_rx) = mpsc::channel::<std::thread::ThreadId>();
365
366 let task = background.spawn_dedicated(move |_executor| async move {
367 let _guard = DropSignal { tx: Some(drop_tx) };
371 started_tx
372 .send(std::thread::current().id())
373 .expect("started signal must be received");
374 });
377
378 let dedicated_thread_id = started_rx
379 .recv_timeout(Duration::from_secs(2))
380 .expect("dedicated future failed to start");
381 assert_ne!(
382 dedicated_thread_id,
383 std::thread::current().id(),
384 "dedicated future ran on the test thread"
385 );
386
387 futures::executor::block_on(task);
389
390 let drop_thread_id = drop_rx
394 .recv_timeout(Duration::from_secs(2))
395 .expect("dedicated future's captured state was not dropped after task completion");
396 assert_eq!(
397 drop_thread_id, dedicated_thread_id,
398 "dedicated future's captured state must be dropped on the dedicated thread, not elsewhere"
399 );
400 }
401
402 #[test]
403 fn spawn_dedicated_detached_child_outlives_root() {
404 use std::sync::mpsc;
405
406 let background =
407 BackgroundExecutor::new(Arc::new(PlatformScheduler::new(Arc::new(SmokeDispatcher))));
408
409 let (gate_tx, gate_rx) = mpsc::channel::<()>();
412 let (child_done_tx, child_done_rx) = mpsc::channel::<std::thread::ThreadId>();
413
414 let task = background.spawn_dedicated(move |executor| async move {
415 executor
416 .spawn(async move {
417 gate_rx
423 .recv()
424 .expect("gate sender dropped before child resumed");
425 child_done_tx
426 .send(std::thread::current().id())
427 .expect("child_done receiver dropped");
428 })
429 .detach();
430 });
433
434 futures::executor::block_on(task);
435
436 assert!(
439 child_done_rx
440 .recv_timeout(Duration::from_millis(50))
441 .is_err(),
442 "detached child finished before being released"
443 );
444
445 gate_tx.send(()).expect("gate receiver dropped");
448
449 let child_thread_id = child_done_rx
450 .recv_timeout(Duration::from_secs(2))
451 .expect("detached child failed to complete after gate was released");
452 assert_ne!(
453 child_thread_id,
454 std::thread::current().id(),
455 "detached child ran on the test thread instead of the dedicated thread"
456 );
457 }
458}