| 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(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 | |