inset_foundation/
executor.rs1use std::collections::VecDeque;
14use std::future::Future;
15use std::pin::Pin;
16use std::sync::{Arc, Mutex, MutexGuard};
17use std::task::{Context, Poll};
18
19use async_task::Runnable;
20use inset_embedder::Dispatcher;
21
22use crate::background;
23
24const POLL_BUDGET: usize = 100_000;
27
28type ReadyQueue = Arc<Mutex<VecDeque<Runnable>>>;
31
32pub(crate) struct ForegroundExecutor {
36 shared: ExecutorHandle,
37}
38
39impl ForegroundExecutor {
40 pub(crate) fn new(dispatcher: Arc<dyn Dispatcher>) -> ForegroundExecutor {
43 ForegroundExecutor {
44 shared: ExecutorHandle {
45 ready: Arc::default(),
46 dispatcher,
47 },
48 }
49 }
50
51 pub(crate) fn spawn<R: 'static>(&self, future: impl Future<Output = R> + 'static) -> Task<R> {
54 self.shared.spawn(future)
55 }
56
57 pub(crate) fn run_in_background<T: Send + 'static>(
59 &self,
60 work: impl FnOnce() -> T + Send + 'static,
61 ) -> Task<T> {
62 self.shared.run_in_background(work)
63 }
64
65 pub(crate) fn handle(&self) -> ExecutorHandle {
66 self.shared.clone()
67 }
68}
69
70impl Drop for ForegroundExecutor {
73 fn drop(&mut self) {
74 loop {
75 let queued: Vec<_> = lock(&self.shared.ready).drain(..).collect();
76 if queued.is_empty() {
77 break;
78 }
79 drop(queued);
80 }
81 }
82}
83
84#[derive(Clone)]
87pub(crate) struct ExecutorHandle {
88 ready: ReadyQueue,
89 dispatcher: Arc<dyn Dispatcher>,
90}
91
92impl ExecutorHandle {
93 pub(crate) fn spawn<R: 'static>(&self, future: impl Future<Output = R> + 'static) -> Task<R> {
96 spawn_on(&self.ready, &self.dispatcher, future)
97 }
98
99 pub(crate) fn run_in_background<T: Send + 'static>(
102 &self,
103 work: impl FnOnce() -> T + Send + 'static,
104 ) -> Task<T> {
105 self.spawn(background::run(&self.dispatcher, work))
106 }
107
108 pub(crate) fn poll_ready_tasks(&self) -> usize {
116 let mut polled = 0usize;
117 loop {
118 let next = lock(&self.ready).pop_front();
119 let Some(runnable) = next else {
120 break;
121 };
122 polled += 1;
123 assert!(
124 polled <= POLL_BUDGET,
125 "tasks did not converge after {POLL_BUDGET} polls; are two tasks waking each \
126 other in a cycle?"
127 );
128 runnable.run();
129 }
130 polled
131 }
132}
133
134fn spawn_on<R: 'static>(
135 ready: &ReadyQueue,
136 dispatcher: &Arc<dyn Dispatcher>,
137 future: impl Future<Output = R> + 'static,
138) -> Task<R> {
139 let ready = Arc::clone(ready);
140 let host = Arc::clone(dispatcher);
141 let schedule = move |runnable| {
146 lock(&ready).push_back(runnable);
147 host.wake_now();
148 };
149 let (runnable, task) = async_task::spawn_local(future, schedule);
150 runnable.schedule();
151 Task::spawned(task)
152}
153
154fn lock(ready: &ReadyQueue) -> MutexGuard<'_, VecDeque<Runnable>> {
155 ready
156 .lock()
157 .expect("the ready queue is never poisoned: nothing runs under its lock")
158}
159
160pub struct Task<T> {
166 inner: TaskInner<T>,
167}
168
169enum TaskInner<T> {
170 Ready(Option<T>),
172 Spawned(Option<async_task::Task<T>>),
173}
174
175impl<T> Task<T> {
176 pub fn ready(value: T) -> Task<T> {
179 Task {
180 inner: TaskInner::Ready(Some(value)),
181 }
182 }
183
184 pub(crate) fn spawned(task: async_task::Task<T>) -> Task<T> {
185 Task {
186 inner: TaskInner::Spawned(Some(task)),
187 }
188 }
189}
190
191impl<T> Unpin for Task<T> {}
194
195impl<T> Future for Task<T> {
196 type Output = T;
197
198 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
199 match &mut self.get_mut().inner {
200 TaskInner::Ready(value) => Poll::Ready(
201 value
202 .take()
203 .expect("a Task is polled until it completes, then never again"),
204 ),
205 TaskInner::Spawned(task) => {
206 let task = task
207 .as_mut()
208 .expect("a Task is polled until it completes, then never again");
209 Pin::new(task).poll(cx)
210 }
211 }
212 }
213}
214
215impl<T> Drop for Task<T> {
216 fn drop(&mut self) {
217 if let TaskInner::Spawned(task) = &mut self.inner
218 && let Some(task) = task.take()
219 {
220 task.detach();
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use std::cell::Cell;
228 use std::future::poll_fn;
229 use std::rc::Rc;
230 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
231 use std::task::Waker;
232
233 use inset_embedder::Instant;
234
235 use super::*;
236
237 struct CountingDispatcher(AtomicUsize);
239
240 impl Dispatcher for CountingDispatcher {
241 fn wake_at(&self, _timer_wakeup: Option<Instant>) {}
242
243 fn wake_now(&self) {
244 self.0.fetch_add(1, Ordering::AcqRel);
245 }
246
247 fn dispatch(&self, work: Box<dyn FnOnce() + Send>) {
248 work();
249 }
250 }
251
252 fn executor() -> (ForegroundExecutor, Arc<CountingDispatcher>) {
253 let host = Arc::new(CountingDispatcher(AtomicUsize::new(0)));
254 let executor = ForegroundExecutor::new(Arc::clone(&host) as Arc<dyn Dispatcher>);
255 (executor, host)
256 }
257
258 fn turns_asked(host: &CountingDispatcher) -> usize {
259 host.0.load(Ordering::Acquire)
260 }
261
262 #[test]
263 fn a_spawn_asks_the_host_for_a_turn_and_the_task_runs_in_it() {
264 let (executor, host) = executor();
265 drop(executor.spawn(async {}));
266 assert_eq!(turns_asked(&host), 1);
267 assert_eq!(executor.handle().poll_ready_tasks(), 1);
268 }
269
270 #[test]
271 fn a_task_woken_from_another_thread_asks_for_a_turn_and_runs_in_it() {
272 let (executor, host) = executor();
273 let parked: Arc<Mutex<Option<Waker>>> = Arc::default();
274 let done = Arc::new(AtomicBool::new(false));
275 let ran = Rc::new(Cell::new(false));
276 let (slot, flag, seen) = (Arc::clone(&parked), Arc::clone(&done), Rc::clone(&ran));
277 drop(executor.spawn(async move {
278 poll_fn(|cx| {
279 if flag.load(Ordering::Acquire) {
280 Poll::Ready(())
281 } else {
282 *slot.lock().unwrap() = Some(cx.waker().clone());
283 Poll::Pending
284 }
285 })
286 .await;
287 seen.set(true);
288 }));
289 let handle = executor.handle();
290 handle.poll_ready_tasks();
291 assert_eq!(
292 turns_asked(&host),
293 1,
294 "the spawn asked for a wake, which parked the task"
295 );
296
297 std::thread::spawn(move || {
298 done.store(true, Ordering::Release);
299 let waker = parked.lock().unwrap().take();
300 waker.expect("the wake parked the waker").wake();
301 })
302 .join()
303 .unwrap();
304 assert_eq!(
305 turns_asked(&host),
306 2,
307 "the worker's wake asks the host for another wake"
308 );
309 assert!(!ran.get(), "nothing runs outside the main thread's wake");
310 handle.poll_ready_tasks();
311 assert!(ran.get());
312 }
313}