1 | //! This crate contains what aims to be the simplest possible implementation of a valid executor. |
2 | //! Instead of nicely parking the thread and waiting for the future to wake it up, it continuously |
3 | //! polls the future until the future is ready. This will probably use a lot of CPU, so be careful |
4 | //! when you use it. |
5 | //! |
6 | //! ```rust |
7 | //! assert_eq!(12, spin_on::spin_on(async {3 * 4})) |
8 | //! ``` |
9 | //! |
10 | //! The advantages of this crate are: |
11 | //! |
12 | //! - It is really simple |
13 | //! - It should work on basically any platform |
14 | //! - It has no dependency on `std` or on an allocator |
15 | //! - It only has one dependency |
16 | //! |
17 | //! ## The Design |
18 | //! |
19 | //! This crate intentionally violates one of the guidelines of `Future`: as of Rust 1.46, the |
20 | //! [runtime characteristics](https://doc.rust-lang.org/1.46.0/core/future/trait.Future.html#runtime-characteristics) |
21 | //! of `core::future::Future` says: |
22 | //! |
23 | //! > The `poll` function is not called repeatedly in a tight loop -- instead, it should only be |
24 | //! called when the future indicates that it is ready to make progress (by calling `wake()`). |
25 | //! |
26 | //! When no Future can make progress, a well-behaved executor should suspend execution and |
27 | //! wait until an external event resumes execution. As far as I know, though, there is not a |
28 | //! cross-platform way to suspend a thread. With Rust's `std`, this would be done by using |
29 | //! `thread::park`. But, for example, if you're on an embedded board with an ARM Cortex M processor, |
30 | //! you would instead use a WFE or WFI instruction. So, an execution-suspending executor would need |
31 | //! to be adapted for each different platform. |
32 | //! |
33 | //! What price do we pay for violating this guideline? This executor is a "resource hog," since it |
34 | //! continually runs the CPU at 100%. On an embedded system, this could cause increased power usage. |
35 | //! In a situation where many programs are running, this could make your application waste CPU |
36 | //! resources that could otherwise be put to good use by other applications. |
37 | //! |
38 | //! When might this be useful? |
39 | //! |
40 | //! - Running async applications on a platform that doesn't have an executor |
41 | //! - Testing that an async crate works with `no_std` |
42 | //! - Educational purposes? |
43 | //! - Implementing an application where you don't care about performance |
44 | #![no_std ] |
45 | |
46 | use core::future::Future; |
47 | use core::sync::atomic::spin_loop_hint; |
48 | use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; |
49 | |
50 | // TODO audit that this noop waker implementations aren't doing anything bad |
51 | unsafe fn rwclone(_p: *const ()) -> RawWaker { |
52 | noop_waker() |
53 | } |
54 | |
55 | unsafe fn rwwake(_p: *const ()) {} |
56 | |
57 | unsafe fn rwwakebyref(_p: *const ()) {} |
58 | |
59 | unsafe fn rwdrop(_p: *const ()) {} |
60 | |
61 | static VTABLE: RawWakerVTable = RawWakerVTable::new(clone:rwclone, wake:rwwake, wake_by_ref:rwwakebyref, drop:rwdrop); |
62 | |
63 | /// The simplest way to create a noop waker in Rust. You would only ever want to use this with |
64 | /// an executor that polls continuously. Thanks to user 2e71828 on |
65 | /// [this Rust forum post](https://users.rust-lang.org/t/simplest-possible-block-on/48364/2). |
66 | fn noop_waker() -> RawWaker { |
67 | static DATA: () = (); |
68 | RawWaker::new(&DATA, &VTABLE) |
69 | } |
70 | |
71 | /// Continuously poll a future until it returns `Poll::Ready`. This is not normally how an |
72 | /// executor should work, because it runs the CPU at 100%. |
73 | pub fn spin_on<F: Future>(future: F) -> F::Output { |
74 | pin_utils::pin_mut!(future); |
75 | let waker: &Waker = &unsafe { Waker::from_raw(waker:noop_waker()) }; |
76 | let mut cx: Context<'_> = Context::from_waker(waker); |
77 | loop { |
78 | if let Poll::Ready(output: ::Output) = future.as_mut().poll(&mut cx) { |
79 | return output; |
80 | } |
81 | spin_loop_hint(); |
82 | } |
83 | } |
84 | |
85 | #[cfg (test)] |
86 | mod tests { |
87 | use core::future::Future; |
88 | use core::pin::Pin; |
89 | use core::task::{Context, Poll}; |
90 | |
91 | struct CountFuture(usize); |
92 | |
93 | impl Future for CountFuture { |
94 | type Output = (); |
95 | |
96 | fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { |
97 | if self.0 > 0 { |
98 | self.0 -= 1; |
99 | cx.waker().wake_by_ref(); |
100 | Poll::Pending |
101 | } else { |
102 | Poll::Ready(()) |
103 | } |
104 | } |
105 | } |
106 | |
107 | #[test ] |
108 | fn ready() { |
109 | crate::spin_on(async {}); |
110 | } |
111 | |
112 | #[test ] |
113 | fn count() { |
114 | crate::spin_on(CountFuture(10)); |
115 | } |
116 | } |
117 | |