1use crate::{
2 CompletionGuard, Executor, ExecutorBlockOn, ExecutorBlocking, ExecutorTimeout, InnerJoinHandle,
3 JoinHandle, abortable_result,
4};
5use futures::future::{AbortHandle, BoxFuture};
6use futures::task::AtomicWaker;
7use parking_lot::Mutex;
8use pollable_map::optional::Optional;
9use std::fmt::{Debug, Formatter};
10use std::future::Future;
11use std::sync::atomic::{AtomicBool, Ordering};
12use std::sync::{Arc, LazyLock};
13use std::task::{Context, Poll, Wake, Waker};
14
15thread_local! {
16 static WAKER_LOCAL_THREAD: Waker = Waker::from(Arc::new(LocalWaker(std::thread::current())));
17}
18
19static LITE_EXECUTOR: LazyLock<LiteRuntimeExecutor> = LazyLock::new(LiteRuntimeExecutor::default);
20
21struct LocalWaker(std::thread::Thread);
22
23impl Wake for LocalWaker {
24 fn wake(self: Arc<Self>) {
25 self.0.unpark();
26 }
27
28 fn wake_by_ref(self: &Arc<Self>) {
29 self.0.unpark();
30 }
31}
32
33struct DriveGuard<'a>(&'a AtomicBool);
34
35impl Drop for DriveGuard<'_> {
36 fn drop(&mut self) {
37 self.0.store(false, Ordering::Release);
38 }
39}
40
41struct ActiveTasks<'a> {
42 queue: &'a Mutex<Vec<BoxFuture<'static, ()>>>,
43 tasks: Vec<BoxFuture<'static, ()>>,
44}
45
46impl Drop for ActiveTasks<'_> {
47 fn drop(&mut self) {
48 self.queue.lock().append(&mut self.tasks);
49 }
50}
51
52#[derive(Default, Clone, Copy, Debug, PartialOrd, PartialEq, Eq)]
57pub struct LiteExecutor;
58
59impl Executor for LiteExecutor {
60 fn runtime_type(&self) -> Option<&'static str> {
61 Some("lite")
62 }
63
64 fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
65 where
66 F: Future + Send + 'static,
67 F::Output: Send + 'static,
68 {
69 LITE_EXECUTOR.spawn(future)
70 }
71}
72
73impl ExecutorBlocking for LiteExecutor {
74 fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
75 where
76 F: FnOnce() -> R + Send + 'static,
77 R: Send + 'static,
78 {
79 LITE_EXECUTOR.spawn_blocking(f)
80 }
81}
82
83impl ExecutorTimeout for LiteExecutor {}
84
85impl ExecutorBlockOn for LiteExecutor {
86 fn block_on<F: Future>(&self, future: F) -> F::Output {
87 LITE_EXECUTOR.block_on(future)
88 }
89}
90
91#[derive(Default, Clone)]
96pub struct LiteRuntimeExecutor {
97 queued_tasks: Arc<Mutex<Vec<BoxFuture<'static, ()>>>>,
98 waker: Arc<AtomicWaker>,
99 driving: Arc<AtomicBool>,
100}
101
102impl Debug for LiteRuntimeExecutor {
103 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
104 f.debug_struct("LiteRuntimeExecutor").finish()
105 }
106}
107
108impl PartialEq for LiteRuntimeExecutor {
109 fn eq(&self, other: &Self) -> bool {
110 Arc::ptr_eq(&self.queued_tasks, &other.queued_tasks)
111 }
112}
113
114impl Eq for LiteRuntimeExecutor {}
115
116impl LiteRuntimeExecutor {
117 pub fn new() -> Self {
119 Self::default()
120 }
121
122 fn take_queued_tasks(&self, tasks: &mut Vec<BoxFuture<'static, ()>>) -> bool {
123 let mut queued = self.queued_tasks.lock();
124 if queued.is_empty() {
125 return false;
126 }
127
128 tasks.append(&mut queued);
129 true
130 }
131}
132
133impl Executor for LiteRuntimeExecutor {
134 fn runtime_type(&self) -> Option<&'static str> {
135 Some("lite")
136 }
137
138 fn spawn<F>(&self, future: F) -> JoinHandle<F::Output>
139 where
140 F: Future + Send + 'static,
141 F::Output: Send + 'static,
142 {
143 let (abort_handle, abort_registration) = AbortHandle::new_pair();
144 let future = abortable_result(future, abort_registration);
145 let (tx, rx) = futures::channel::oneshot::channel();
146 let finished = Arc::new(AtomicBool::new(false));
147 let completion = CompletionGuard::new(finished.clone());
148 let task = async move {
149 let _completion = completion;
150 let result = future.await;
151 let _ = tx.send(result);
152 };
153
154 self.queued_tasks.lock().push(Box::pin(task));
155 self.waker.wake();
156
157 let inner = InnerJoinHandle::CustomHandle {
158 inner: Optional::new(rx),
159 handle: abort_handle,
160 finished,
161 };
162
163 JoinHandle { inner }
164 }
165}
166
167impl ExecutorBlocking for LiteRuntimeExecutor {
168 fn spawn_blocking<F, R>(&self, f: F) -> JoinHandle<R>
169 where
170 F: FnOnce() -> R + Send + 'static,
171 R: Send + 'static,
172 {
173 self.spawn(async move {
174 let (tx, rx) = futures::channel::oneshot::channel();
175 let _handle = std::thread::spawn(move || {
176 let result = f();
177 let _ = tx.send(result);
178 });
179 rx.await.expect("blocking task should not be dropped")
180 })
181 }
182}
183
184impl ExecutorTimeout for LiteRuntimeExecutor {}
185
186impl ExecutorBlockOn for LiteRuntimeExecutor {
187 fn block_on<F: Future>(&self, future: F) -> F::Output {
188 if self
189 .driving
190 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
191 .is_err()
192 {
193 panic!("LiteRuntimeExecutor is already being driven");
194 }
195 let _drive_guard = DriveGuard(&self.driving);
196 let mut future = core::pin::pin!(future);
197 let mut active = ActiveTasks {
198 queue: &self.queued_tasks,
199 tasks: Vec::new(),
200 };
201
202 WAKER_LOCAL_THREAD.with(|waker| {
203 let mut context = Context::from_waker(waker);
204
205 loop {
206 self.waker.register(waker);
207 self.take_queued_tasks(&mut active.tasks);
208
209 if let Poll::Ready(output) = future.as_mut().poll(&mut context) {
210 return output;
211 }
212
213 let mut made_progress = false;
214 let mut index = 0;
215 while index < active.tasks.len() {
216 if active.tasks[index].as_mut().poll(&mut context).is_ready() {
217 drop(active.tasks.swap_remove(index));
218 made_progress = true;
219 } else {
220 index += 1;
221 }
222 }
223
224 if self.take_queued_tasks(&mut active.tasks) || made_progress {
225 continue;
226 }
227
228 self.waker.register(waker);
229 if self.take_queued_tasks(&mut active.tasks) {
230 continue;
231 }
232
233 std::thread::park();
234 }
235 })
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::{LiteExecutor, LiteRuntimeExecutor};
242 use crate::error::JoinError;
243 use crate::{Executor, ExecutorBlockOn, ExecutorBlocking, ExecutorTimeout};
244 use std::panic::AssertUnwindSafe;
245 use std::time::Duration;
246
247 #[test]
248 fn ready_future_completes() {
249 assert_eq!(LiteRuntimeExecutor::new().block_on(async { 42 }), 42);
250 }
251
252 #[test]
253 fn shared_executor_drives_spawned_tasks() {
254 let handle = LiteExecutor.spawn(async { 42 });
255
256 assert_eq!(LiteExecutor.block_on(handle).unwrap(), 42);
257 }
258
259 #[test]
260 fn spawned_task_completes() {
261 let executor = LiteRuntimeExecutor::new();
262 let handle = executor.spawn(async { 42 });
263
264 assert_eq!(executor.block_on(handle).unwrap(), 42);
265 }
266
267 #[test]
268 fn nested_spawn_completes() {
269 let executor = LiteRuntimeExecutor::new();
270 let nested_executor = executor.clone();
271 let handle =
272 executor.spawn(async move { nested_executor.spawn(async { 41 }).await.unwrap() + 1 });
273
274 assert_eq!(executor.block_on(handle).unwrap(), 42);
275 }
276
277 #[test]
278 fn task_spawned_from_another_thread_wakes_driver() {
279 let executor = LiteRuntimeExecutor::new();
280 let other_executor = executor.clone();
281 let (start_tx, start_rx) = std::sync::mpsc::channel();
282 let (value_tx, value_rx) = futures::channel::oneshot::channel();
283
284 let worker = std::thread::spawn(move || {
285 start_rx.recv().unwrap();
286 std::thread::sleep(Duration::from_millis(10));
287 other_executor.dispatch(async move {
288 value_tx.send(42).unwrap();
289 });
290 });
291
292 let value = executor.block_on(async move {
293 start_tx.send(()).unwrap();
294 value_rx.await.unwrap()
295 });
296
297 worker.join().unwrap();
298 assert_eq!(value, 42);
299 }
300
301 #[test]
302 fn unfinished_tasks_are_preserved() {
303 let executor = LiteRuntimeExecutor::new();
304 let handle = executor.spawn(async { 42 });
305
306 executor.block_on(async {});
307
308 assert!(!handle.is_finished());
309 assert_eq!(executor.block_on(handle).unwrap(), 42);
310 }
311
312 #[cfg(panic = "unwind")]
313 #[test]
314 fn unfinished_tasks_are_preserved_after_block_on_panics() {
315 let executor = LiteRuntimeExecutor::new();
316 let handle = executor.spawn(async { 42 });
317
318 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
319 executor.block_on(async { panic!("expected driver panic") });
320 }));
321
322 assert!(result.is_err());
323 assert!(!handle.is_finished());
324 assert_eq!(executor.block_on(handle).unwrap(), 42);
325 }
326
327 #[test]
328 fn spawn_blocking_completes() {
329 let executor = LiteRuntimeExecutor::new();
330 let handle = executor.spawn_blocking(|| 42);
331
332 assert_eq!(executor.block_on(handle).unwrap(), 42);
333 }
334
335 #[test]
336 fn timeout_completes() {
337 let executor = LiteRuntimeExecutor::new();
338 let handle = executor.spawn_timeout(Duration::from_millis(10), async {
339 futures::future::pending::<()>().await
340 });
341
342 assert!(executor.block_on(handle).unwrap().is_err());
343 }
344
345 #[cfg(panic = "unwind")]
346 #[test]
347 fn task_panic_is_classified() {
348 let executor = LiteRuntimeExecutor::new();
349 let handle = executor.spawn(async { panic!("expected task panic") });
350
351 assert!(matches!(
352 executor.block_on(handle),
353 Err(JoinError::Panicked)
354 ));
355 }
356
357 #[test]
358 fn explicit_abort_is_reported_as_aborted() {
359 let executor = LiteRuntimeExecutor::new();
360 let handle = executor.spawn(futures::future::pending::<()>());
361 handle.abort();
362
363 assert!(matches!(executor.block_on(handle), Err(JoinError::Aborted)));
364 }
365
366 #[cfg(panic = "unwind")]
367 #[test]
368 fn concurrent_block_on_panics() {
369 let executor = LiteRuntimeExecutor::new();
370 let other_executor = executor.clone();
371 let (started_tx, started_rx) = std::sync::mpsc::channel();
372 let (release_tx, release_rx) = futures::channel::oneshot::channel();
373
374 let driver = std::thread::spawn(move || {
375 other_executor.block_on(async move {
376 started_tx.send(()).unwrap();
377 release_rx.await.unwrap();
378 });
379 });
380 started_rx.recv().unwrap();
381
382 let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
383 executor.block_on(async {});
384 }));
385
386 release_tx.send(()).unwrap();
387 driver.join().unwrap();
388 assert!(result.is_err());
389 }
390}