diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index e2e7bce3..43d94e54 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - Replaced Pender. Implementations now must define an extern function called `__pender`. +- Made `raw::AvailableTask` public +- Made `SpawnToken::new_failed` public ## 0.2.1 - 2023-08-10 diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7caa3302..c1d82e18 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -147,10 +147,7 @@ impl TaskStorage { pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { let task = AvailableTask::claim(self); match task { - Some(task) => { - let task = task.initialize(future); - unsafe { SpawnToken::::new(task) } - } + Some(task) => task.initialize(future), None => SpawnToken::new_failed(), } } @@ -186,12 +183,16 @@ impl TaskStorage { } } -struct AvailableTask { +/// An uninitialized [`TaskStorage`]. +pub struct AvailableTask { task: &'static TaskStorage, } impl AvailableTask { - fn claim(task: &'static TaskStorage) -> Option { + /// Try to claim a [`TaskStorage`]. + /// + /// This function returns `None` if a task has already been spawned and has not finished running. + pub fn claim(task: &'static TaskStorage) -> Option { task.raw .state .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) @@ -199,61 +200,30 @@ impl AvailableTask { .map(|_| Self { task }) } - fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { + fn initialize_impl(self, future: impl FnOnce() -> F) -> SpawnToken { unsafe { self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); self.task.future.write(future()); - } - TaskRef::new(self.task) - } -} -/// Raw storage that can hold up to N tasks of the same type. -/// -/// This is essentially a `[TaskStorage; N]`. -pub struct TaskPool { - pool: [TaskStorage; N], -} + let task = TaskRef::new(self.task); -impl TaskPool { - /// Create a new TaskPool, with all tasks in non-spawned state. - pub const fn new() -> Self { - Self { - pool: [TaskStorage::NEW; N], + SpawnToken::new(task) } } - /// Try to spawn a task in the pool. - /// - /// See [`TaskStorage::spawn()`] for details. - /// - /// This will loop over the pool and spawn the task in the first storage that - /// is currently free. If none is free, a "poisoned" SpawnToken is returned, - /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. - pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - let task = self.pool.iter().find_map(AvailableTask::claim); - match task { - Some(task) => { - let task = task.initialize(future); - unsafe { SpawnToken::::new(task) } - } - None => SpawnToken::new_failed(), - } + /// Initialize the [`TaskStorage`] to run the given future. + pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken { + self.initialize_impl::(future) } - /// Like spawn(), but allows the task to be send-spawned if the args are Send even if - /// the future is !Send. + /// Initialize the [`TaskStorage`] to run the given future. /// - /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used - /// by the Embassy macros ONLY. + /// # Safety /// - /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` /// is an `async fn`, NOT a hand-written `Future`. #[doc(hidden)] - pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken - where - FutFn: FnOnce() -> F, - { + pub unsafe fn __initialize_async_fn(self, future: impl FnOnce() -> F) -> SpawnToken { // When send-spawning a task, we construct the future in this thread, and effectively // "send" it to the executor thread by enqueuing it in its queue. Therefore, in theory, // send-spawning should require the future `F` to be `Send`. @@ -279,16 +249,59 @@ impl TaskPool { // // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. + self.initialize_impl::(future) + } +} - let task = self.pool.iter().find_map(AvailableTask::claim); - match task { - Some(task) => { - let task = task.initialize(future); - unsafe { SpawnToken::::new(task) } - } +/// Raw storage that can hold up to N tasks of the same type. +/// +/// This is essentially a `[TaskStorage; N]`. +pub struct TaskPool { + pool: [TaskStorage; N], +} + +impl TaskPool { + /// Create a new TaskPool, with all tasks in non-spawned state. + pub const fn new() -> Self { + Self { + pool: [TaskStorage::NEW; N], + } + } + + fn spawn_impl(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + match self.pool.iter().find_map(AvailableTask::claim) { + Some(task) => task.initialize_impl::(future), None => SpawnToken::new_failed(), } } + + /// Try to spawn a task in the pool. + /// + /// See [`TaskStorage::spawn()`] for details. + /// + /// This will loop over the pool and spawn the task in the first storage that + /// is currently free. If none is free, a "poisoned" SpawnToken is returned, + /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. + pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { + self.spawn_impl::(future) + } + + /// Like spawn(), but allows the task to be send-spawned if the args are Send even if + /// the future is !Send. + /// + /// Not covered by semver guarantees. DO NOT call this directly. Intended to be used + /// by the Embassy macros ONLY. + /// + /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` + /// is an `async fn`, NOT a hand-written `Future`. + #[doc(hidden)] + pub unsafe fn _spawn_async_fn(&'static self, future: FutFn) -> SpawnToken + where + FutFn: FnOnce() -> F, + { + // See the comment in AvailableTask::__initialize_async_fn for explanation. + self.spawn_impl::(future) + } } #[derive(Clone, Copy)] diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 2b622404..5a3a0dee 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -33,7 +33,8 @@ impl SpawnToken { } } - pub(crate) fn new_failed() -> Self { + /// Return a SpawnToken that represents a failed spawn. + pub fn new_failed() -> Self { Self { raw_task: None, phantom: PhantomData,