diff options
| author | Dario Nieuwenhuis <[email protected]> | 2021-08-26 00:20:52 +0200 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2021-08-31 23:59:28 +0200 |
| commit | 297de612e5ea01a2b6cc4921ff7c2e133dba4ac2 (patch) | |
| tree | 0aa880ea46a707e4621563555b158b40e19992c9 | |
| parent | e56c6166dcac9132cde1769e5ef8d60e03963329 (diff) | |
Improve executor naming. Add docs.
| -rw-r--r-- | embassy-macros/src/lib.rs | 8 | ||||
| -rw-r--r-- | embassy/src/executor/arch/arm.rs | 75 | ||||
| -rw-r--r-- | embassy/src/executor/arch/std.rs | 23 | ||||
| -rw-r--r-- | embassy/src/executor/mod.rs | 4 | ||||
| -rw-r--r-- | embassy/src/executor/raw/mod.rs | 139 | ||||
| -rw-r--r-- | embassy/src/executor/raw/run_queue.rs | 30 | ||||
| -rw-r--r-- | embassy/src/executor/raw/waker.rs | 11 | ||||
| -rw-r--r-- | embassy/src/executor/spawner.rs | 41 | ||||
| -rw-r--r-- | embassy/src/time/driver.rs | 7 | ||||
| -rw-r--r-- | embassy/src/time/mod.rs | 2 | ||||
| -rw-r--r-- | examples/nrf/src/bin/raw_spawn.rs | 6 |
11 files changed, 290 insertions, 56 deletions
diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs index ddcee0cb1..54cf6fb8a 100644 --- a/embassy-macros/src/lib.rs +++ b/embassy-macros/src/lib.rs | |||
| @@ -115,12 +115,12 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { | |||
| 115 | let result = quote! { | 115 | let result = quote! { |
| 116 | #(#attrs)* | 116 | #(#attrs)* |
| 117 | #visibility fn #name(#args) -> #embassy_path::executor::SpawnToken<#impl_ty> { | 117 | #visibility fn #name(#args) -> #embassy_path::executor::SpawnToken<#impl_ty> { |
| 118 | use #embassy_path::executor::raw::Task; | 118 | use #embassy_path::executor::raw::TaskStorage; |
| 119 | #task_fn | 119 | #task_fn |
| 120 | type F = #impl_ty; | 120 | type F = #impl_ty; |
| 121 | const NEW_TASK: Task<F> = Task::new(); | 121 | const NEW_TASK: TaskStorage<F> = TaskStorage::new(); |
| 122 | static POOL: [Task<F>; #pool_size] = [NEW_TASK; #pool_size]; | 122 | static POOL: [TaskStorage<F>; #pool_size] = [NEW_TASK; #pool_size]; |
| 123 | unsafe { Task::spawn_pool(&POOL, move || task(#arg_names)) } | 123 | unsafe { TaskStorage::spawn_pool(&POOL, move || task(#arg_names)) } |
| 124 | } | 124 | } |
| 125 | }; | 125 | }; |
| 126 | result.into() | 126 | result.into() |
diff --git a/embassy/src/executor/arch/arm.rs b/embassy/src/executor/arch/arm.rs index 4fd734cd7..d23a595ff 100644 --- a/embassy/src/executor/arch/arm.rs +++ b/embassy/src/executor/arch/arm.rs | |||
| @@ -4,12 +4,23 @@ use core::ptr; | |||
| 4 | use super::{raw, Spawner}; | 4 | use super::{raw, Spawner}; |
| 5 | use crate::interrupt::{Interrupt, InterruptExt}; | 5 | use crate::interrupt::{Interrupt, InterruptExt}; |
| 6 | 6 | ||
| 7 | /// Thread mode executor, using WFE/SEV. | ||
| 8 | /// | ||
| 9 | /// This is the simplest and most common kind of executor. It runs on | ||
| 10 | /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction | ||
| 11 | /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction | ||
| 12 | /// is executed, to make the `WFE` exit from sleep and poll the task. | ||
| 13 | /// | ||
| 14 | /// This executor allows for ultra low power consumption for chips where `WFE` | ||
| 15 | /// triggers low-power sleep without extra steps. If your chip requires extra steps, | ||
| 16 | /// you may use [`raw::Executor`] directly to program custom behavior. | ||
| 7 | pub struct Executor { | 17 | pub struct Executor { |
| 8 | inner: raw::Executor, | 18 | inner: raw::Executor, |
| 9 | not_send: PhantomData<*mut ()>, | 19 | not_send: PhantomData<*mut ()>, |
| 10 | } | 20 | } |
| 11 | 21 | ||
| 12 | impl Executor { | 22 | impl Executor { |
| 23 | /// Create a new Executor. | ||
| 13 | pub fn new() -> Self { | 24 | pub fn new() -> Self { |
| 14 | Self { | 25 | Self { |
| 15 | inner: raw::Executor::new(|_| cortex_m::asm::sev(), ptr::null_mut()), | 26 | inner: raw::Executor::new(|_| cortex_m::asm::sev(), ptr::null_mut()), |
| @@ -17,14 +28,29 @@ impl Executor { | |||
| 17 | } | 28 | } |
| 18 | } | 29 | } |
| 19 | 30 | ||
| 20 | /// Runs the executor. | 31 | /// Run the executor. |
| 32 | /// | ||
| 33 | /// The `init` closure is called with a [`Spawner`] that spawns tasks on | ||
| 34 | /// this executor. Use it to spawn the initial task(s). After `init` returns, | ||
| 35 | /// the executor starts running the tasks. | ||
| 36 | /// | ||
| 37 | /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
| 38 | /// for example by passing it as an argument to the initial tasks. | ||
| 39 | /// | ||
| 40 | /// This function requires `&'static mut self`. This means you have to store the | ||
| 41 | /// Executor instance in a place where it'll live forever and grants you mutable | ||
| 42 | /// access. There's a few ways to do this: | ||
| 43 | /// | ||
| 44 | /// - a [Forever](crate::util::Forever) (safe) | ||
| 45 | /// - a `static mut` (unsafe) | ||
| 46 | /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
| 21 | /// | 47 | /// |
| 22 | /// This function never returns. | 48 | /// This function never returns. |
| 23 | pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { | 49 | pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { |
| 24 | init(unsafe { self.inner.spawner() }); | 50 | init(self.inner.spawner()); |
| 25 | 51 | ||
| 26 | loop { | 52 | loop { |
| 27 | unsafe { self.inner.run_queued() }; | 53 | unsafe { self.inner.poll() }; |
| 28 | cortex_m::asm::wfe(); | 54 | cortex_m::asm::wfe(); |
| 29 | } | 55 | } |
| 30 | } | 56 | } |
| @@ -41,6 +67,27 @@ fn pend_by_number(n: u16) { | |||
| 41 | cortex_m::peripheral::NVIC::pend(N(n)) | 67 | cortex_m::peripheral::NVIC::pend(N(n)) |
| 42 | } | 68 | } |
| 43 | 69 | ||
| 70 | /// Interrupt mode executor. | ||
| 71 | /// | ||
| 72 | /// This executor runs tasks in interrupt mode. The interrupt handler is set up | ||
| 73 | /// to poll tasks, and when a task is woken the interrupt is pended from software. | ||
| 74 | /// | ||
| 75 | /// This allows running async tasks at a priority higher than thread mode. One | ||
| 76 | /// use case is to leave thread mode free for non-async tasks. Another use case is | ||
| 77 | /// to run multiple executors: one in thread mode for low priority tasks and another in | ||
| 78 | /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower | ||
| 79 | /// priority ones. | ||
| 80 | /// | ||
| 81 | /// It is even possible to run multiple interrupt mode executors at different priorities, | ||
| 82 | /// by assigning different priorities to the interrupts. For an example on how to do this, | ||
| 83 | /// See the 'multiprio' example for 'embassy-nrf'. | ||
| 84 | /// | ||
| 85 | /// To use it, you have to pick an interrupt that won't be used by the hardware. | ||
| 86 | /// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). | ||
| 87 | /// If this is not the case, you may use an interrupt from any unused peripheral. | ||
| 88 | /// | ||
| 89 | /// It is somewhat more complex to use, it's recommended to use the thread-mode | ||
| 90 | /// [`Executor`] instead, if it works for your use case. | ||
| 44 | pub struct InterruptExecutor<I: Interrupt> { | 91 | pub struct InterruptExecutor<I: Interrupt> { |
| 45 | irq: I, | 92 | irq: I, |
| 46 | inner: raw::Executor, | 93 | inner: raw::Executor, |
| @@ -48,6 +95,7 @@ pub struct InterruptExecutor<I: Interrupt> { | |||
| 48 | } | 95 | } |
| 49 | 96 | ||
| 50 | impl<I: Interrupt> InterruptExecutor<I> { | 97 | impl<I: Interrupt> InterruptExecutor<I> { |
| 98 | /// Create a new Executor. | ||
| 51 | pub fn new(irq: I) -> Self { | 99 | pub fn new(irq: I) -> Self { |
| 52 | let ctx = irq.number() as *mut (); | 100 | let ctx = irq.number() as *mut (); |
| 53 | Self { | 101 | Self { |
| @@ -59,16 +107,29 @@ impl<I: Interrupt> InterruptExecutor<I> { | |||
| 59 | 107 | ||
| 60 | /// Start the executor. | 108 | /// Start the executor. |
| 61 | /// | 109 | /// |
| 62 | /// `init` is called in the interrupt context, then the interrupt is | 110 | /// The `init` closure is called from interrupt mode, with a [`Spawner`] that spawns tasks on |
| 63 | /// configured to run the executor. | 111 | /// this executor. Use it to spawn the initial task(s). After `init` returns, |
| 112 | /// the interrupt is configured so that the executor starts running the tasks. | ||
| 113 | /// Once the executor is started, `start` returns. | ||
| 114 | /// | ||
| 115 | /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
| 116 | /// for example by passing it as an argument to the initial tasks. | ||
| 117 | /// | ||
| 118 | /// This function requires `&'static mut self`. This means you have to store the | ||
| 119 | /// Executor instance in a place where it'll live forever and grants you mutable | ||
| 120 | /// access. There's a few ways to do this: | ||
| 121 | /// | ||
| 122 | /// - a [Forever](crate::util::Forever) (safe) | ||
| 123 | /// - a `static mut` (unsafe) | ||
| 124 | /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
| 64 | pub fn start(&'static mut self, init: impl FnOnce(Spawner) + Send) { | 125 | pub fn start(&'static mut self, init: impl FnOnce(Spawner) + Send) { |
| 65 | self.irq.disable(); | 126 | self.irq.disable(); |
| 66 | 127 | ||
| 67 | init(unsafe { self.inner.spawner() }); | 128 | init(self.inner.spawner()); |
| 68 | 129 | ||
| 69 | self.irq.set_handler(|ctx| unsafe { | 130 | self.irq.set_handler(|ctx| unsafe { |
| 70 | let executor = &*(ctx as *const raw::Executor); | 131 | let executor = &*(ctx as *const raw::Executor); |
| 71 | executor.run_queued(); | 132 | executor.poll(); |
| 72 | }); | 133 | }); |
| 73 | self.irq.set_handler_context(&self.inner as *const _ as _); | 134 | self.irq.set_handler_context(&self.inner as *const _ as _); |
| 74 | self.irq.enable(); | 135 | self.irq.enable(); |
diff --git a/embassy/src/executor/arch/std.rs b/embassy/src/executor/arch/std.rs index fb7880542..b93ab8a79 100644 --- a/embassy/src/executor/arch/std.rs +++ b/embassy/src/executor/arch/std.rs | |||
| @@ -3,6 +3,7 @@ use std::sync::{Condvar, Mutex}; | |||
| 3 | 3 | ||
| 4 | use super::{raw, Spawner}; | 4 | use super::{raw, Spawner}; |
| 5 | 5 | ||
| 6 | /// Single-threaded std-based executor. | ||
| 6 | pub struct Executor { | 7 | pub struct Executor { |
| 7 | inner: raw::Executor, | 8 | inner: raw::Executor, |
| 8 | not_send: PhantomData<*mut ()>, | 9 | not_send: PhantomData<*mut ()>, |
| @@ -10,6 +11,7 @@ pub struct Executor { | |||
| 10 | } | 11 | } |
| 11 | 12 | ||
| 12 | impl Executor { | 13 | impl Executor { |
| 14 | /// Create a new Executor. | ||
| 13 | pub fn new() -> Self { | 15 | pub fn new() -> Self { |
| 14 | let signaler = &*Box::leak(Box::new(Signaler::new())); | 16 | let signaler = &*Box::leak(Box::new(Signaler::new())); |
| 15 | Self { | 17 | Self { |
| @@ -25,14 +27,29 @@ impl Executor { | |||
| 25 | } | 27 | } |
| 26 | } | 28 | } |
| 27 | 29 | ||
| 28 | /// Runs the executor. | 30 | /// Run the executor. |
| 31 | /// | ||
| 32 | /// The `init` closure is called with a [`Spawner`] that spawns tasks on | ||
| 33 | /// this executor. Use it to spawn the initial task(s). After `init` returns, | ||
| 34 | /// the executor starts running the tasks. | ||
| 35 | /// | ||
| 36 | /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), | ||
| 37 | /// for example by passing it as an argument to the initial tasks. | ||
| 38 | /// | ||
| 39 | /// This function requires `&'static mut self`. This means you have to store the | ||
| 40 | /// Executor instance in a place where it'll live forever and grants you mutable | ||
| 41 | /// access. There's a few ways to do this: | ||
| 42 | /// | ||
| 43 | /// - a [Forever](crate::util::Forever) (safe) | ||
| 44 | /// - a `static mut` (unsafe) | ||
| 45 | /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) | ||
| 29 | /// | 46 | /// |
| 30 | /// This function never returns. | 47 | /// This function never returns. |
| 31 | pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { | 48 | pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { |
| 32 | init(unsafe { self.inner.spawner() }); | 49 | init(self.inner.spawner()); |
| 33 | 50 | ||
| 34 | loop { | 51 | loop { |
| 35 | unsafe { self.inner.run_queued() }; | 52 | unsafe { self.inner.poll() }; |
| 36 | self.signaler.wait() | 53 | self.signaler.wait() |
| 37 | } | 54 | } |
| 38 | } | 55 | } |
diff --git a/embassy/src/executor/mod.rs b/embassy/src/executor/mod.rs index 5ffbe689e..1f6bdd277 100644 --- a/embassy/src/executor/mod.rs +++ b/embassy/src/executor/mod.rs | |||
| @@ -1,3 +1,7 @@ | |||
| 1 | //! Async task executor. | ||
| 2 | |||
| 3 | #![deny(missing_docs)] | ||
| 4 | |||
| 1 | #[cfg_attr(feature = "std", path = "arch/std.rs")] | 5 | #[cfg_attr(feature = "std", path = "arch/std.rs")] |
| 2 | #[cfg_attr(not(feature = "std"), path = "arch/arm.rs")] | 6 | #[cfg_attr(not(feature = "std"), path = "arch/arm.rs")] |
| 3 | mod arch; | 7 | mod arch; |
diff --git a/embassy/src/executor/raw/mod.rs b/embassy/src/executor/raw/mod.rs index 235a09198..05fb29758 100644 --- a/embassy/src/executor/raw/mod.rs +++ b/embassy/src/executor/raw/mod.rs | |||
| @@ -1,3 +1,12 @@ | |||
| 1 | //! Raw executor. | ||
| 2 | //! | ||
| 3 | //! This module exposes "raw" Executor and Task structs for more low level control. | ||
| 4 | //! | ||
| 5 | //! ## WARNING: here be dragons! | ||
| 6 | //! | ||
| 7 | //! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe | ||
| 8 | //! executor wrappers in [`crate::executor`] and the [`crate::task`] macro, which are fully safe. | ||
| 9 | |||
| 1 | mod run_queue; | 10 | mod run_queue; |
| 2 | #[cfg(feature = "time")] | 11 | #[cfg(feature = "time")] |
| 3 | mod timer_queue; | 12 | mod timer_queue; |
| @@ -30,6 +39,10 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; | |||
| 30 | #[cfg(feature = "time")] | 39 | #[cfg(feature = "time")] |
| 31 | pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; | 40 | pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; |
| 32 | 41 | ||
| 42 | /// Raw task header for use in task pointers. | ||
| 43 | /// | ||
| 44 | /// This is an opaque struct, used for raw pointers to tasks, for use | ||
| 45 | /// with funtions like [`wake_task`] and [`task_from_waker`]. | ||
| 33 | pub struct TaskHeader { | 46 | pub struct TaskHeader { |
| 34 | pub(crate) state: AtomicU32, | 47 | pub(crate) state: AtomicU32, |
| 35 | pub(crate) run_queue_item: RunQueueItem, | 48 | pub(crate) run_queue_item: RunQueueItem, |
| @@ -85,15 +98,29 @@ impl TaskHeader { | |||
| 85 | } | 98 | } |
| 86 | } | 99 | } |
| 87 | 100 | ||
| 101 | /// Raw storage in which a task can be spawned. | ||
| 102 | /// | ||
| 103 | /// This struct holds the necessary memory to spawn one task whose future is `F`. | ||
| 104 | /// At a given time, the `Task` may be in spawned or not-spawned state. You may spawn it | ||
| 105 | /// with [`Task::spawn()`], which will fail if it is already spawned. | ||
| 106 | /// | ||
| 107 | /// A `TaskStorage` must live forever, it may not be deallocated even after the task has finished | ||
| 108 | /// running. Hence the relevant methods require `&'static self`. It may be reused, however. | ||
| 109 | /// | ||
| 110 | /// Internally, the [embassy::task](crate::task) macro allocates an array of `TaskStorage`s | ||
| 111 | /// in a `static`. The most common reason to use the raw `Task` is to have control of where | ||
| 112 | /// the memory for the task is allocated: on the stack, or on the heap with e.g. `Box::leak`, etc. | ||
| 113 | |||
| 88 | // repr(C) is needed to guarantee that the Task is located at offset 0 | 114 | // repr(C) is needed to guarantee that the Task is located at offset 0 |
| 89 | // This makes it safe to cast between Task and Task pointers. | 115 | // This makes it safe to cast between Task and Task pointers. |
| 90 | #[repr(C)] | 116 | #[repr(C)] |
| 91 | pub struct Task<F: Future + 'static> { | 117 | pub struct TaskStorage<F: Future + 'static> { |
| 92 | raw: TaskHeader, | 118 | raw: TaskHeader, |
| 93 | future: UninitCell<F>, // Valid if STATE_SPAWNED | 119 | future: UninitCell<F>, // Valid if STATE_SPAWNED |
| 94 | } | 120 | } |
| 95 | 121 | ||
| 96 | impl<F: Future + 'static> Task<F> { | 122 | impl<F: Future + 'static> TaskStorage<F> { |
| 123 | /// Create a new Task, in not-spawned state. | ||
| 97 | pub const fn new() -> Self { | 124 | pub const fn new() -> Self { |
| 98 | Self { | 125 | Self { |
| 99 | raw: TaskHeader::new(), | 126 | raw: TaskHeader::new(), |
| @@ -101,6 +128,12 @@ impl<F: Future + 'static> Task<F> { | |||
| 101 | } | 128 | } |
| 102 | } | 129 | } |
| 103 | 130 | ||
| 131 | /// Try to spawn a task in a pool. | ||
| 132 | /// | ||
| 133 | /// See [`Self::spawn()`] for details. | ||
| 134 | /// | ||
| 135 | /// This will loop over the pool and spawn the task in the first storage that | ||
| 136 | /// is currently free. If none is free, | ||
| 104 | pub fn spawn_pool(pool: &'static [Self], future: impl FnOnce() -> F) -> SpawnToken<F> { | 137 | pub fn spawn_pool(pool: &'static [Self], future: impl FnOnce() -> F) -> SpawnToken<F> { |
| 105 | for task in pool { | 138 | for task in pool { |
| 106 | if task.spawn_allocate() { | 139 | if task.spawn_allocate() { |
| @@ -111,6 +144,19 @@ impl<F: Future + 'static> Task<F> { | |||
| 111 | SpawnToken::new_failed() | 144 | SpawnToken::new_failed() |
| 112 | } | 145 | } |
| 113 | 146 | ||
| 147 | /// Try to spawn the task. | ||
| 148 | /// | ||
| 149 | /// The `future` closure constructs the future. It's only called if spawning is | ||
| 150 | /// actually possible. It is a closure instead of a simple `future: F` param to ensure | ||
| 151 | /// the future is constructed in-place, avoiding a temporary copy in the stack thanks to | ||
| 152 | /// NRVO optimizations. | ||
| 153 | /// | ||
| 154 | /// This function will fail if the task is already spawned and has not finished running. | ||
| 155 | /// In this case, the error is delayed: a "poisoned" SpawnToken is returned, which will | ||
| 156 | /// cause [`Executor::spawn()`] to return the error. | ||
| 157 | /// | ||
| 158 | /// Once the task has finished running, you may spawn it again. It is allowed to spawn it | ||
| 159 | /// on a different executor. | ||
| 114 | pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<F> { | 160 | pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<F> { |
| 115 | if self.spawn_allocate() { | 161 | if self.spawn_allocate() { |
| 116 | unsafe { self.spawn_initialize(future) } | 162 | unsafe { self.spawn_initialize(future) } |
| @@ -136,7 +182,7 @@ impl<F: Future + 'static> Task<F> { | |||
| 136 | } | 182 | } |
| 137 | 183 | ||
| 138 | unsafe fn poll(p: NonNull<TaskHeader>) { | 184 | unsafe fn poll(p: NonNull<TaskHeader>) { |
| 139 | let this = &*(p.as_ptr() as *const Task<F>); | 185 | let this = &*(p.as_ptr() as *const TaskStorage<F>); |
| 140 | 186 | ||
| 141 | let future = Pin::new_unchecked(this.future.as_mut()); | 187 | let future = Pin::new_unchecked(this.future.as_mut()); |
| 142 | let waker = waker::from_task(p); | 188 | let waker = waker::from_task(p); |
| @@ -155,8 +201,27 @@ impl<F: Future + 'static> Task<F> { | |||
| 155 | } | 201 | } |
| 156 | } | 202 | } |
| 157 | 203 | ||
| 158 | unsafe impl<F: Future + 'static> Sync for Task<F> {} | 204 | unsafe impl<F: Future + 'static> Sync for TaskStorage<F> {} |
| 159 | 205 | ||
| 206 | /// Raw executor. | ||
| 207 | /// | ||
| 208 | /// This is the core of the Embassy executor. It is low-level, requiring manual | ||
| 209 | /// handling of wakeups and task polling. If you can, prefer using one of the | ||
| 210 | /// higher level executors in [`crate::executor`]. | ||
| 211 | /// | ||
| 212 | /// The raw executor leaves it up to you to handle wakeups and scheduling: | ||
| 213 | /// | ||
| 214 | /// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks | ||
| 215 | /// that "want to run"). | ||
| 216 | /// - You must supply a `signal_fn`. The executor will call it to notify you it has work | ||
| 217 | /// to do. You must arrange for `poll()` to be called as soon as possible. | ||
| 218 | /// | ||
| 219 | /// `signal_fn` can be called from *any* context: any thread, any interrupt priority | ||
| 220 | /// level, etc. It may be called synchronously from any `Executor` method call as well. | ||
| 221 | /// You must deal with this correctly. | ||
| 222 | /// | ||
| 223 | /// In particular, you must NOT call `poll` directly from `signal_fn`, as this violates | ||
| 224 | /// the requirement for `poll` to not be called reentrantly. | ||
| 160 | pub struct Executor { | 225 | pub struct Executor { |
| 161 | run_queue: RunQueue, | 226 | run_queue: RunQueue, |
| 162 | signal_fn: fn(*mut ()), | 227 | signal_fn: fn(*mut ()), |
| @@ -169,6 +234,12 @@ pub struct Executor { | |||
| 169 | } | 234 | } |
| 170 | 235 | ||
| 171 | impl Executor { | 236 | impl Executor { |
| 237 | /// Create a new executor. | ||
| 238 | /// | ||
| 239 | /// When the executor has work to do, it will call `signal_fn` with | ||
| 240 | /// `signal_ctx` as argument. | ||
| 241 | /// | ||
| 242 | /// See [`Executor`] docs for details on `signal_fn`. | ||
| 172 | pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self { | 243 | pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self { |
| 173 | #[cfg(feature = "time")] | 244 | #[cfg(feature = "time")] |
| 174 | let alarm = unsafe { unwrap!(driver::allocate_alarm()) }; | 245 | let alarm = unsafe { unwrap!(driver::allocate_alarm()) }; |
| @@ -187,23 +258,51 @@ impl Executor { | |||
| 187 | } | 258 | } |
| 188 | } | 259 | } |
| 189 | 260 | ||
| 190 | pub fn set_signal_ctx(&mut self, signal_ctx: *mut ()) { | 261 | /// Enqueue a task in the task queue |
| 191 | self.signal_ctx = signal_ctx; | 262 | /// |
| 192 | } | 263 | /// # Safety |
| 193 | 264 | /// - `task` must be a valid pointer to a spawned task. | |
| 194 | unsafe fn enqueue(&self, item: *mut TaskHeader) { | 265 | /// - `task` must be set up to run in this executor. |
| 195 | if self.run_queue.enqueue(item) { | 266 | /// - `task` must NOT be already enqueued (in this executor or another one). |
| 267 | unsafe fn enqueue(&self, task: *mut TaskHeader) { | ||
| 268 | if self.run_queue.enqueue(task) { | ||
| 196 | (self.signal_fn)(self.signal_ctx) | 269 | (self.signal_fn)(self.signal_ctx) |
| 197 | } | 270 | } |
| 198 | } | 271 | } |
| 199 | 272 | ||
| 200 | pub unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) { | 273 | /// Spawn a task in this executor. |
| 274 | /// | ||
| 275 | /// # Safety | ||
| 276 | /// | ||
| 277 | /// `task` must be a valid pointer to an initialized but not-already-spawned task. | ||
| 278 | /// | ||
| 279 | /// It is OK to use `unsafe` to call this from a thread that's not the executor thread. | ||
| 280 | /// In this case, the task's Future must be Send. This is because this is effectively | ||
| 281 | /// sending the task to the executor thread. | ||
| 282 | pub(super) unsafe fn spawn(&'static self, task: NonNull<TaskHeader>) { | ||
| 201 | let task = task.as_ref(); | 283 | let task = task.as_ref(); |
| 202 | task.executor.set(self); | 284 | task.executor.set(self); |
| 203 | self.enqueue(task as *const _ as _); | 285 | self.enqueue(task as *const _ as _); |
| 204 | } | 286 | } |
| 205 | 287 | ||
| 206 | pub unsafe fn run_queued(&'static self) { | 288 | /// Poll all queued tasks in this executor. |
| 289 | /// | ||
| 290 | /// This loops over all tasks that are queued to be polled (i.e. they're | ||
| 291 | /// freshly spawned or they've been woken). Other tasks are not polled. | ||
| 292 | /// | ||
| 293 | /// You must call `poll` after receiving a call to `signal_fn`. It is OK | ||
| 294 | /// to call `poll` even when not requested by `signal_fn`, but it wastes | ||
| 295 | /// energy. | ||
| 296 | /// | ||
| 297 | /// # Safety | ||
| 298 | /// | ||
| 299 | /// You must NOT call `poll` reentrantly on the same executor. | ||
| 300 | /// | ||
| 301 | /// In particular, note that `poll` may call `signal_fn` synchronously. Therefore, you | ||
| 302 | /// must NOT directly call `poll()` from your `signal_fn`. Instead, `signal_fn` has to | ||
| 303 | /// somehow schedule for `poll()` to be called later, at a time you know for sure there's | ||
| 304 | /// no `poll()` already running. | ||
| 305 | pub unsafe fn poll(&'static self) { | ||
| 207 | #[cfg(feature = "time")] | 306 | #[cfg(feature = "time")] |
| 208 | self.timer_queue.dequeue_expired(Instant::now(), |p| { | 307 | self.timer_queue.dequeue_expired(Instant::now(), |p| { |
| 209 | p.as_ref().enqueue(); | 308 | p.as_ref().enqueue(); |
| @@ -235,18 +334,26 @@ impl Executor { | |||
| 235 | 334 | ||
| 236 | #[cfg(feature = "time")] | 335 | #[cfg(feature = "time")] |
| 237 | { | 336 | { |
| 238 | // If this is in the past, set_alarm will immediately trigger the alarm, | 337 | // If this is already in the past, set_alarm will immediately trigger the alarm. |
| 239 | // which will make the wfe immediately return so we do another loop iteration. | 338 | // This will cause `signal_fn` to be called, which will cause `poll()` to be called again, |
| 339 | // so we immediately do another poll loop iteration. | ||
| 240 | let next_expiration = self.timer_queue.next_expiration(); | 340 | let next_expiration = self.timer_queue.next_expiration(); |
| 241 | driver::set_alarm(self.alarm, next_expiration.as_ticks()); | 341 | driver::set_alarm(self.alarm, next_expiration.as_ticks()); |
| 242 | } | 342 | } |
| 243 | } | 343 | } |
| 244 | 344 | ||
| 245 | pub unsafe fn spawner(&'static self) -> super::Spawner { | 345 | /// Get a spawner that spawns tasks in this executor. |
| 346 | /// | ||
| 347 | /// It is OK to call this method multiple times to obtain multiple | ||
| 348 | /// `Spawner`s. You may also copy `Spawner`s. | ||
| 349 | pub fn spawner(&'static self) -> super::Spawner { | ||
| 246 | super::Spawner::new(self) | 350 | super::Spawner::new(self) |
| 247 | } | 351 | } |
| 248 | } | 352 | } |
| 249 | 353 | ||
| 354 | /// Wake a task by raw pointer. | ||
| 355 | /// | ||
| 356 | /// You can obtain task pointers from `Waker`s using [`task_from_waker`]. | ||
| 250 | pub unsafe fn wake_task(task: NonNull<TaskHeader>) { | 357 | pub unsafe fn wake_task(task: NonNull<TaskHeader>) { |
| 251 | task.as_ref().enqueue(); | 358 | task.as_ref().enqueue(); |
| 252 | } | 359 | } |
diff --git a/embassy/src/executor/raw/run_queue.rs b/embassy/src/executor/raw/run_queue.rs index 8e8bc8ff3..d0258c717 100644 --- a/embassy/src/executor/raw/run_queue.rs +++ b/embassy/src/executor/raw/run_queue.rs | |||
| @@ -39,13 +39,17 @@ impl RunQueue { | |||
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | /// Enqueues an item. Returns true if the queue was empty. | 41 | /// Enqueues an item. Returns true if the queue was empty. |
| 42 | pub(crate) unsafe fn enqueue(&self, item: *mut TaskHeader) -> bool { | 42 | /// |
| 43 | /// # Safety | ||
| 44 | /// | ||
| 45 | /// `item` must NOT be already enqueued in any queue. | ||
| 46 | pub(crate) unsafe fn enqueue(&self, task: *mut TaskHeader) -> bool { | ||
| 43 | let mut prev = self.head.load(Ordering::Acquire); | 47 | let mut prev = self.head.load(Ordering::Acquire); |
| 44 | loop { | 48 | loop { |
| 45 | (*item).run_queue_item.next.store(prev, Ordering::Relaxed); | 49 | (*task).run_queue_item.next.store(prev, Ordering::Relaxed); |
| 46 | match self | 50 | match self |
| 47 | .head | 51 | .head |
| 48 | .compare_exchange_weak(prev, item, Ordering::AcqRel, Ordering::Acquire) | 52 | .compare_exchange_weak(prev, task, Ordering::AcqRel, Ordering::Acquire) |
| 49 | { | 53 | { |
| 50 | Ok(_) => break, | 54 | Ok(_) => break, |
| 51 | Err(next_prev) => prev = next_prev, | 55 | Err(next_prev) => prev = next_prev, |
| @@ -55,17 +59,25 @@ impl RunQueue { | |||
| 55 | prev.is_null() | 59 | prev.is_null() |
| 56 | } | 60 | } |
| 57 | 61 | ||
| 58 | pub(crate) unsafe fn dequeue_all(&self, on_task: impl Fn(NonNull<TaskHeader>)) { | 62 | /// Empty the queue, then call `on_task` for each task that was in the queue. |
| 59 | let mut task = self.head.swap(ptr::null_mut(), Ordering::AcqRel); | 63 | /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue |
| 64 | /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. | ||
| 65 | pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull<TaskHeader>)) { | ||
| 66 | // Atomically empty the queue. | ||
| 67 | let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); | ||
| 60 | 68 | ||
| 61 | while !task.is_null() { | 69 | // Iterate the linked list of tasks that were previously in the queue. |
| 70 | while let Some(task) = NonNull::new(ptr) { | ||
| 62 | // If the task re-enqueues itself, the `next` pointer will get overwritten. | 71 | // If the task re-enqueues itself, the `next` pointer will get overwritten. |
| 63 | // Therefore, first read the next pointer, and only then process the task. | 72 | // Therefore, first read the next pointer, and only then process the task. |
| 64 | let next = (*task).run_queue_item.next.load(Ordering::Relaxed); | 73 | let next = unsafe { task.as_ref() } |
| 74 | .run_queue_item | ||
| 75 | .next | ||
| 76 | .load(Ordering::Relaxed); | ||
| 65 | 77 | ||
| 66 | on_task(NonNull::new_unchecked(task)); | 78 | on_task(task); |
| 67 | 79 | ||
| 68 | task = next | 80 | ptr = next |
| 69 | } | 81 | } |
| 70 | } | 82 | } |
| 71 | } | 83 | } |
diff --git a/embassy/src/executor/raw/waker.rs b/embassy/src/executor/raw/waker.rs index e53190f17..7cd847697 100644 --- a/embassy/src/executor/raw/waker.rs +++ b/embassy/src/executor/raw/waker.rs | |||
| @@ -22,6 +22,17 @@ pub(crate) unsafe fn from_task(p: NonNull<TaskHeader>) -> Waker { | |||
| 22 | Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) | 22 | Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | /// Get a task pointer from a waker. | ||
| 26 | /// | ||
| 27 | /// This can used as an optimization in wait queues to store task pointers | ||
| 28 | /// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps | ||
| 29 | /// avoid dynamic dispatch. | ||
| 30 | /// | ||
| 31 | /// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). | ||
| 32 | /// | ||
| 33 | /// # Panics | ||
| 34 | /// | ||
| 35 | /// Panics if the waker is not created by the Embassy executor. | ||
| 25 | pub unsafe fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> { | 36 | pub unsafe fn task_from_waker(waker: &Waker) -> NonNull<TaskHeader> { |
| 26 | let hack: &WakerHack = mem::transmute(waker); | 37 | let hack: &WakerHack = mem::transmute(waker); |
| 27 | if hack.vtable != &VTABLE { | 38 | if hack.vtable != &VTABLE { |
diff --git a/embassy/src/executor/spawner.rs b/embassy/src/executor/spawner.rs index 36100aecb..908e139ae 100644 --- a/embassy/src/executor/spawner.rs +++ b/embassy/src/executor/spawner.rs | |||
| @@ -4,7 +4,17 @@ use core::ptr::NonNull; | |||
| 4 | 4 | ||
| 5 | use super::raw; | 5 | use super::raw; |
| 6 | 6 | ||
| 7 | #[must_use = "Calling a task function does nothing on its own. You must pass the returned SpawnToken to Executor::spawn()"] | 7 | /// Token to spawn a newly-created task in an executor. |
| 8 | /// | ||
| 9 | /// When calling a task function (like `#[embassy::task] async fn my_task() { ... }`), the returned | ||
| 10 | /// value is a `SpawnToken` that represents an instance of the task, ready to spawn. You must | ||
| 11 | /// then spawn it into an executor, typically with [`Spawner::spawn()`]. | ||
| 12 | /// | ||
| 13 | /// # Panics | ||
| 14 | /// | ||
| 15 | /// Dropping a SpawnToken instance panics. You may not "abort" spawning a task in this way. | ||
| 16 | /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. | ||
| 17 | #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] | ||
| 8 | pub struct SpawnToken<F> { | 18 | pub struct SpawnToken<F> { |
| 9 | raw_task: Option<NonNull<raw::TaskHeader>>, | 19 | raw_task: Option<NonNull<raw::TaskHeader>>, |
| 10 | phantom: PhantomData<*mut F>, | 20 | phantom: PhantomData<*mut F>, |
| @@ -29,13 +39,19 @@ impl<F> SpawnToken<F> { | |||
| 29 | impl<F> Drop for SpawnToken<F> { | 39 | impl<F> Drop for SpawnToken<F> { |
| 30 | fn drop(&mut self) { | 40 | fn drop(&mut self) { |
| 31 | // TODO deallocate the task instead. | 41 | // TODO deallocate the task instead. |
| 32 | panic!("SpawnToken instances may not be dropped. You must pass them to Executor::spawn()") | 42 | panic!("SpawnToken instances may not be dropped. You must pass them to Spawner::spawn()") |
| 33 | } | 43 | } |
| 34 | } | 44 | } |
| 35 | 45 | ||
| 46 | /// Error returned when spawning a task. | ||
| 36 | #[derive(Copy, Clone, Debug)] | 47 | #[derive(Copy, Clone, Debug)] |
| 37 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 48 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 38 | pub enum SpawnError { | 49 | pub enum SpawnError { |
| 50 | /// Too many instances of this task are already running. | ||
| 51 | /// | ||
| 52 | /// By default, a task marked with `#[embassy::task]` can only have one instance | ||
| 53 | /// running at a time. You may allow multiple instances to run in parallel with | ||
| 54 | /// `#[embassy::task(pool_size = 4)]`, at the cost of higher RAM usage. | ||
| 39 | Busy, | 55 | Busy, |
| 40 | } | 56 | } |
| 41 | 57 | ||
| @@ -52,13 +68,16 @@ pub struct Spawner { | |||
| 52 | } | 68 | } |
| 53 | 69 | ||
| 54 | impl Spawner { | 70 | impl Spawner { |
| 55 | pub(crate) unsafe fn new(executor: &'static raw::Executor) -> Self { | 71 | pub(crate) fn new(executor: &'static raw::Executor) -> Self { |
| 56 | Self { | 72 | Self { |
| 57 | executor, | 73 | executor, |
| 58 | not_send: PhantomData, | 74 | not_send: PhantomData, |
| 59 | } | 75 | } |
| 60 | } | 76 | } |
| 61 | 77 | ||
| 78 | /// Spawn a task into an executor. | ||
| 79 | /// | ||
| 80 | /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy::task]). | ||
| 62 | pub fn spawn<F>(&self, token: SpawnToken<F>) -> Result<(), SpawnError> { | 81 | pub fn spawn<F>(&self, token: SpawnToken<F>) -> Result<(), SpawnError> { |
| 63 | let task = token.raw_task; | 82 | let task = token.raw_task; |
| 64 | mem::forget(token); | 83 | mem::forget(token); |
| @@ -93,10 +112,11 @@ impl Spawner { | |||
| 93 | 112 | ||
| 94 | /// Handle to spawn tasks into an executor from any thread. | 113 | /// Handle to spawn tasks into an executor from any thread. |
| 95 | /// | 114 | /// |
| 96 | /// This Spawner can be used from any thread (it implements Send and Sync, so after any task (Send and non-Send ones), but it can | 115 | /// This Spawner can be used from any thread (it is Send), but it can |
| 97 | /// only be used in the executor thread (it is not Send itself). | 116 | /// only spawn Send tasks. The reason for this is spawning is effectively |
| 117 | /// "sending" the tasks to the executor thread. | ||
| 98 | /// | 118 | /// |
| 99 | /// If you want to spawn tasks from another thread, use [SendSpawner]. | 119 | /// If you want to spawn non-Send tasks, use [Spawner]. |
| 100 | #[derive(Copy, Clone)] | 120 | #[derive(Copy, Clone)] |
| 101 | pub struct SendSpawner { | 121 | pub struct SendSpawner { |
| 102 | executor: &'static raw::Executor, | 122 | executor: &'static raw::Executor, |
| @@ -106,13 +126,10 @@ pub struct SendSpawner { | |||
| 106 | unsafe impl Send for SendSpawner {} | 126 | unsafe impl Send for SendSpawner {} |
| 107 | unsafe impl Sync for SendSpawner {} | 127 | unsafe impl Sync for SendSpawner {} |
| 108 | 128 | ||
| 109 | /// Handle to spawn tasks to an executor. | ||
| 110 | /// | ||
| 111 | /// This Spawner can spawn any task (Send and non-Send ones), but it can | ||
| 112 | /// only be used in the executor thread (it is not Send itself). | ||
| 113 | /// | ||
| 114 | /// If you want to spawn tasks from another thread, use [SendSpawner]. | ||
| 115 | impl SendSpawner { | 129 | impl SendSpawner { |
| 130 | /// Spawn a task into an executor. | ||
| 131 | /// | ||
| 132 | /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy::task]). | ||
| 116 | pub fn spawn<F: Send>(&self, token: SpawnToken<F>) -> Result<(), SpawnError> { | 133 | pub fn spawn<F: Send>(&self, token: SpawnToken<F>) -> Result<(), SpawnError> { |
| 117 | let header = token.raw_task; | 134 | let header = token.raw_task; |
| 118 | mem::forget(token); | 135 | mem::forget(token); |
diff --git a/embassy/src/time/driver.rs b/embassy/src/time/driver.rs index 1b8949ae1..a21a29d46 100644 --- a/embassy/src/time/driver.rs +++ b/embassy/src/time/driver.rs | |||
| @@ -95,12 +95,15 @@ pub trait Driver: Send + Sync + 'static { | |||
| 95 | /// The callback may be called from any context (interrupt or thread mode). | 95 | /// The callback may be called from any context (interrupt or thread mode). |
| 96 | fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); | 96 | fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); |
| 97 | 97 | ||
| 98 | /// Sets an alarm at the given timestamp. When the current timestamp reaches that | 98 | /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm |
| 99 | /// timestamp, the provided callback funcion will be called. | 99 | /// timestamp, the provided callback funcion will be called. |
| 100 | /// | 100 | /// |
| 101 | /// If `timestamp` is already in the past, the alarm callback must be immediately fired. | ||
| 102 | /// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`. | ||
| 103 | /// | ||
| 101 | /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. | 104 | /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. |
| 102 | /// | 105 | /// |
| 103 | /// Only one alarm can be active at a time. This overwrites any previously-set alarm if any. | 106 | /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. |
| 104 | fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); | 107 | fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); |
| 105 | } | 108 | } |
| 106 | 109 | ||
diff --git a/embassy/src/time/mod.rs b/embassy/src/time/mod.rs index e50742040..6ce18d475 100644 --- a/embassy/src/time/mod.rs +++ b/embassy/src/time/mod.rs | |||
| @@ -40,6 +40,8 @@ | |||
| 40 | //! | 40 | //! |
| 41 | //! For more details, check the [`driver`] module. | 41 | //! For more details, check the [`driver`] module. |
| 42 | 42 | ||
| 43 | #![deny(missing_docs)] | ||
| 44 | |||
| 43 | mod delay; | 45 | mod delay; |
| 44 | pub mod driver; | 46 | pub mod driver; |
| 45 | mod duration; | 47 | mod duration; |
diff --git a/examples/nrf/src/bin/raw_spawn.rs b/examples/nrf/src/bin/raw_spawn.rs index 326dd9aac..d0bd68674 100644 --- a/examples/nrf/src/bin/raw_spawn.rs +++ b/examples/nrf/src/bin/raw_spawn.rs | |||
| @@ -8,7 +8,7 @@ use example_common::*; | |||
| 8 | use core::mem; | 8 | use core::mem; |
| 9 | use cortex_m_rt::entry; | 9 | use cortex_m_rt::entry; |
| 10 | 10 | ||
| 11 | use embassy::executor::raw::Task; | 11 | use embassy::executor::raw::TaskStorage; |
| 12 | use embassy::executor::Executor; | 12 | use embassy::executor::Executor; |
| 13 | use embassy::time::{Duration, Timer}; | 13 | use embassy::time::{Duration, Timer}; |
| 14 | use embassy::util::Forever; | 14 | use embassy::util::Forever; |
| @@ -36,8 +36,8 @@ fn main() -> ! { | |||
| 36 | let _p = embassy_nrf::init(Default::default()); | 36 | let _p = embassy_nrf::init(Default::default()); |
| 37 | let executor = EXECUTOR.put(Executor::new()); | 37 | let executor = EXECUTOR.put(Executor::new()); |
| 38 | 38 | ||
| 39 | let run1_task = Task::new(); | 39 | let run1_task = TaskStorage::new(); |
| 40 | let run2_task = Task::new(); | 40 | let run2_task = TaskStorage::new(); |
| 41 | 41 | ||
| 42 | // Safety: these variables do live forever if main never returns. | 42 | // Safety: these variables do live forever if main never returns. |
| 43 | let run1_task = unsafe { make_static(&run1_task) }; | 43 | let run1_task = unsafe { make_static(&run1_task) }; |
