From 66faba2df76bf76bcfcca30d159c5a39b64819e8 Mon Sep 17 00:00:00 2001 From: loris <76793979+lorislibralato@users.noreply.github.com> Date: Sat, 5 Aug 2023 21:04:51 +0200 Subject: add wake_task_no_pend for expired timer enqueue inside run_queue --- embassy-executor/src/raw/mod.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index f3760f589..25c2ab0da 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -409,7 +409,7 @@ impl SyncExecutor { #[allow(clippy::never_loop)] loop { #[cfg(feature = "integrated-timers")] - self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); + self.timer_queue.dequeue_expired(Instant::now(), wake_task_no_pend); self.run_queue.dequeue_all(|p| { let task = p.header(); @@ -573,6 +573,31 @@ pub fn wake_task(task: TaskRef) { } } +/// Wake a task by `TaskRef` without calling pend. +/// +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task_no_pend(task: TaskRef) { + let header = task.header(); + + let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { + // If already scheduled, or if not started, + if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { + None + } else { + // Mark it as scheduled + Some(state | STATE_RUN_QUEUED) + } + }); + + if res.is_ok() { + // We have just marked the task as scheduled, so enqueue it. + unsafe { + let executor = header.executor.get().unwrap_unchecked(); + executor.run_queue.enqueue(task); + } + } +} + #[cfg(feature = "integrated-timers")] struct TimerQueue; -- cgit From 675b7fb6056d8c3dfaca759b7cd373e2f4a0e111 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Sat, 12 Aug 2023 16:00:18 +0200 Subject: POC: allow custom executors --- embassy-executor/src/raw/mod.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 25c2ab0da..b4d70b1e9 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -291,12 +291,29 @@ impl TaskPool { } } +/// Context given to the thread-mode executor's pender. +#[cfg(all(feature = "executor-thread", not(feature = "thread-context")))] +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct OpaqueThreadContext(pub(crate) ()); + +/// Context given to the thread-mode executor's pender. +#[cfg(all(feature = "executor-thread", feature = "thread-context"))] +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct OpaqueThreadContext(pub(crate) usize); + +/// Context given to the interrupt-mode executor's pender. +#[derive(Clone, Copy)] +#[repr(transparent)] +pub struct OpaqueInterruptContext(pub(crate) usize); + #[derive(Clone, Copy)] pub(crate) enum PenderInner { #[cfg(feature = "executor-thread")] - Thread(crate::arch::ThreadPender), + Thread(OpaqueThreadContext), #[cfg(feature = "executor-interrupt")] - Interrupt(crate::arch::InterruptPender), + Interrupt(OpaqueInterruptContext), #[cfg(feature = "pender-callback")] Callback { func: fn(*mut ()), context: *mut () }, } @@ -333,9 +350,19 @@ impl Pender { pub(crate) fn pend(&self) { match self.0 { #[cfg(feature = "executor-thread")] - PenderInner::Thread(x) => x.pend(), + PenderInner::Thread(core_id) => { + extern "Rust" { + fn __thread_mode_pender(core_id: OpaqueThreadContext); + } + unsafe { __thread_mode_pender(core_id) }; + } #[cfg(feature = "executor-interrupt")] - PenderInner::Interrupt(x) => x.pend(), + PenderInner::Interrupt(interrupt) => { + extern "Rust" { + fn __interrupt_mode_pender(interrupt: OpaqueInterruptContext); + } + unsafe { __interrupt_mode_pender(interrupt) }; + } #[cfg(feature = "pender-callback")] PenderInner::Callback { func, context } => func(context), } -- cgit From fbf50cdae899dc1cd2f232b880e096d0fc51f49c Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Sat, 12 Aug 2023 22:05:19 +0200 Subject: Remove Pender wrapper --- embassy-executor/src/raw/mod.rs | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index b4d70b1e9..7fd29db40 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -308,27 +308,30 @@ pub struct OpaqueThreadContext(pub(crate) usize); #[repr(transparent)] pub struct OpaqueInterruptContext(pub(crate) usize); +/// Platform/architecture-specific action executed when an executor has pending work. +/// +/// When a task within an executor is woken, the `Pender` is called. This does a +/// platform/architecture-specific action to signal there is pending work in the executor. +/// When this happens, you must arrange for [`Executor::poll`] to be called. +/// +/// You can think of it as a waker, but for the whole executor. #[derive(Clone, Copy)] -pub(crate) enum PenderInner { +pub enum Pender { + /// Pender for a thread-mode executor. #[cfg(feature = "executor-thread")] Thread(OpaqueThreadContext), + + /// Pender for an interrupt-mode executor. #[cfg(feature = "executor-interrupt")] Interrupt(OpaqueInterruptContext), + + /// Arbitrary, dynamically dispatched pender. #[cfg(feature = "pender-callback")] Callback { func: fn(*mut ()), context: *mut () }, } -unsafe impl Send for PenderInner {} -unsafe impl Sync for PenderInner {} - -/// Platform/architecture-specific action executed when an executor has pending work. -/// -/// When a task within an executor is woken, the `Pender` is called. This does a -/// platform/architecture-specific action to signal there is pending work in the executor. -/// When this happens, you must arrange for [`Executor::poll`] to be called. -/// -/// You can think of it as a waker, but for the whole executor. -pub struct Pender(pub(crate) PenderInner); +unsafe impl Send for Pender {} +unsafe impl Sync for Pender {} impl Pender { /// Create a `Pender` that will call an arbitrary function pointer. @@ -339,32 +342,29 @@ impl Pender { /// - `context`: Opaque context pointer, that will be passed to the function pointer. #[cfg(feature = "pender-callback")] pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { - Self(PenderInner::Callback { - func, - context: context.into(), - }) + Self::Callback { func, context } } } impl Pender { - pub(crate) fn pend(&self) { - match self.0 { + pub(crate) fn pend(self) { + match self { #[cfg(feature = "executor-thread")] - PenderInner::Thread(core_id) => { + Pender::Thread(core_id) => { extern "Rust" { fn __thread_mode_pender(core_id: OpaqueThreadContext); } unsafe { __thread_mode_pender(core_id) }; } #[cfg(feature = "executor-interrupt")] - PenderInner::Interrupt(interrupt) => { + Pender::Interrupt(interrupt) => { extern "Rust" { fn __interrupt_mode_pender(interrupt: OpaqueInterruptContext); } unsafe { __interrupt_mode_pender(interrupt) }; } #[cfg(feature = "pender-callback")] - PenderInner::Callback { func, context } => func(context), + Pender::Callback { func, context } => func(context), } } } -- cgit From ec6bd27df6101bc5f77fa4eace0e8963970231ad Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 08:22:22 +0200 Subject: Remove thread-context feature --- embassy-executor/src/raw/mod.rs | 7 ------- 1 file changed, 7 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7fd29db40..7795f1e4a 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -292,13 +292,6 @@ impl TaskPool { } /// Context given to the thread-mode executor's pender. -#[cfg(all(feature = "executor-thread", not(feature = "thread-context")))] -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct OpaqueThreadContext(pub(crate) ()); - -/// Context given to the thread-mode executor's pender. -#[cfg(all(feature = "executor-thread", feature = "thread-context"))] #[repr(transparent)] #[derive(Clone, Copy)] pub struct OpaqueThreadContext(pub(crate) usize); -- cgit From 454a7cbf4c0eb3a4e651e7da5512ec49ff7d4050 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 08:32:26 +0200 Subject: Remove pender-callback --- embassy-executor/src/raw/mod.rs | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7795f1e4a..81ad1e53d 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -317,28 +317,11 @@ pub enum Pender { /// Pender for an interrupt-mode executor. #[cfg(feature = "executor-interrupt")] Interrupt(OpaqueInterruptContext), - - /// Arbitrary, dynamically dispatched pender. - #[cfg(feature = "pender-callback")] - Callback { func: fn(*mut ()), context: *mut () }, } unsafe impl Send for Pender {} unsafe impl Sync for Pender {} -impl Pender { - /// Create a `Pender` that will call an arbitrary function pointer. - /// - /// # Arguments - /// - /// - `func`: The function pointer to call. - /// - `context`: Opaque context pointer, that will be passed to the function pointer. - #[cfg(feature = "pender-callback")] - pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { - Self::Callback { func, context } - } -} - impl Pender { pub(crate) fn pend(self) { match self { @@ -356,8 +339,6 @@ impl Pender { } unsafe { __interrupt_mode_pender(interrupt) }; } - #[cfg(feature = "pender-callback")] - Pender::Callback { func, context } => func(context), } } } -- cgit From f6007869bffd3ed4f48e74222dc40d11c7c87ec0 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 08:57:14 +0200 Subject: Remove the Pender enum --- embassy-executor/src/raw/mod.rs | 48 ++++++----------------------------------- 1 file changed, 7 insertions(+), 41 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 81ad1e53d..a0a940e25 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -292,54 +292,20 @@ impl TaskPool { } /// Context given to the thread-mode executor's pender. -#[repr(transparent)] -#[derive(Clone, Copy)] -pub struct OpaqueThreadContext(pub(crate) usize); - -/// Context given to the interrupt-mode executor's pender. -#[derive(Clone, Copy)] -#[repr(transparent)] -pub struct OpaqueInterruptContext(pub(crate) usize); +pub type PenderContext = usize; -/// Platform/architecture-specific action executed when an executor has pending work. -/// -/// When a task within an executor is woken, the `Pender` is called. This does a -/// platform/architecture-specific action to signal there is pending work in the executor. -/// When this happens, you must arrange for [`Executor::poll`] to be called. -/// -/// You can think of it as a waker, but for the whole executor. #[derive(Clone, Copy)] -pub enum Pender { - /// Pender for a thread-mode executor. - #[cfg(feature = "executor-thread")] - Thread(OpaqueThreadContext), - - /// Pender for an interrupt-mode executor. - #[cfg(feature = "executor-interrupt")] - Interrupt(OpaqueInterruptContext), -} +pub(crate) struct Pender(PenderContext); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} impl Pender { pub(crate) fn pend(self) { - match self { - #[cfg(feature = "executor-thread")] - Pender::Thread(core_id) => { - extern "Rust" { - fn __thread_mode_pender(core_id: OpaqueThreadContext); - } - unsafe { __thread_mode_pender(core_id) }; - } - #[cfg(feature = "executor-interrupt")] - Pender::Interrupt(interrupt) => { - extern "Rust" { - fn __interrupt_mode_pender(interrupt: OpaqueInterruptContext); - } - unsafe { __interrupt_mode_pender(interrupt) }; - } + extern "Rust" { + fn __pender(context: PenderContext); } + unsafe { __pender(self.0) }; } } @@ -499,9 +465,9 @@ impl Executor { /// When the executor has work to do, it will call the [`Pender`]. /// /// See [`Executor`] docs for details on `Pender`. - pub fn new(pender: Pender) -> Self { + pub fn new(context: PenderContext) -> Self { Self { - inner: SyncExecutor::new(pender), + inner: SyncExecutor::new(Pender(context)), _not_sync: PhantomData, } } -- cgit From 4c4b12c307bf77516299eb73f9da00ef777b9814 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 15:16:40 +0200 Subject: Make PenderContext opaque --- embassy-executor/src/raw/mod.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index a0a940e25..4a6e45535 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -292,10 +292,29 @@ impl TaskPool { } /// Context given to the thread-mode executor's pender. -pub type PenderContext = usize; +#[repr(transparent)] +#[derive(Clone, Copy)] +pub struct PenderContext(usize); +/// Platform/architecture-specific action executed when an executor has pending work. +/// +/// When a task within an executor is woken, the `Pender` is called. This does a +/// platform/architecture-specific action to signal there is pending work in the executor. +/// When this happens, you must arrange for [`Executor::poll`] to be called. +/// +/// You can think of it as a waker, but for the whole executor. +/// +/// Platform/architecture implementations must provide a function that can be referred to as: +/// +/// ```rust +/// use embassy_executor::raw::PenderContext; +/// +/// extern "Rust" { +/// fn __pender(context: PenderContext); +/// } +/// ``` #[derive(Clone, Copy)] -pub(crate) struct Pender(PenderContext); +pub struct Pender(PenderContext); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} -- cgit From 3a51e2d9cae6fad2fd903c07634b4a66de59b3bf Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 15:45:43 +0200 Subject: Make PenderContext actually pointer-size --- embassy-executor/src/raw/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 4a6e45535..2bbbb132c 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -294,7 +294,7 @@ impl TaskPool { /// Context given to the thread-mode executor's pender. #[repr(transparent)] #[derive(Clone, Copy)] -pub struct PenderContext(usize); +pub struct PenderContext(*mut ()); /// Platform/architecture-specific action executed when an executor has pending work. /// -- cgit From 995434614384bc5c218a16a026ce7c06737ca860 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 15:59:47 +0200 Subject: Remove interrupt executor, remove PenderContext --- embassy-executor/src/raw/mod.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 2bbbb132c..aa99b4cf3 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -291,11 +291,6 @@ impl TaskPool { } } -/// Context given to the thread-mode executor's pender. -#[repr(transparent)] -#[derive(Clone, Copy)] -pub struct PenderContext(*mut ()); - /// Platform/architecture-specific action executed when an executor has pending work. /// /// When a task within an executor is woken, the `Pender` is called. This does a @@ -306,15 +301,13 @@ pub struct PenderContext(*mut ()); /// /// Platform/architecture implementations must provide a function that can be referred to as: /// -/// ```rust -/// use embassy_executor::raw::PenderContext; -/// +/// ```rust/// /// extern "Rust" { -/// fn __pender(context: PenderContext); +/// fn __pender(context: *mut ()); /// } /// ``` #[derive(Clone, Copy)] -pub struct Pender(PenderContext); +pub struct Pender(*mut ()); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} @@ -322,7 +315,7 @@ unsafe impl Sync for Pender {} impl Pender { pub(crate) fn pend(self) { extern "Rust" { - fn __pender(context: PenderContext); + fn __pender(context: *mut ()); } unsafe { __pender(self.0) }; } @@ -484,7 +477,7 @@ impl Executor { /// When the executor has work to do, it will call the [`Pender`]. /// /// See [`Executor`] docs for details on `Pender`. - pub fn new(context: PenderContext) -> Self { + pub fn new(context: *mut ()) -> Self { Self { inner: SyncExecutor::new(Pender(context)), _not_sync: PhantomData, -- cgit From 07c36001271ab0a033a08a6535719729efb677c4 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 16:35:12 +0200 Subject: Hide Pender --- embassy-executor/src/raw/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index aa99b4cf3..064831026 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -301,13 +301,13 @@ impl TaskPool { /// /// Platform/architecture implementations must provide a function that can be referred to as: /// -/// ```rust/// +/// ```rust /// extern "Rust" { /// fn __pender(context: *mut ()); /// } /// ``` #[derive(Clone, Copy)] -pub struct Pender(*mut ()); +pub(crate) struct Pender(*mut ()); unsafe impl Send for Pender {} unsafe impl Sync for Pender {} -- cgit From 890f29ccfe129f3205cf835c7131862c579d9349 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 14 Aug 2023 17:53:42 +0200 Subject: Update docs --- embassy-executor/src/raw/mod.rs | 49 +++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 24 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 064831026..7caa3302f 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -291,21 +291,6 @@ impl TaskPool { } } -/// Platform/architecture-specific action executed when an executor has pending work. -/// -/// When a task within an executor is woken, the `Pender` is called. This does a -/// platform/architecture-specific action to signal there is pending work in the executor. -/// When this happens, you must arrange for [`Executor::poll`] to be called. -/// -/// You can think of it as a waker, but for the whole executor. -/// -/// Platform/architecture implementations must provide a function that can be referred to as: -/// -/// ```rust -/// extern "Rust" { -/// fn __pender(context: *mut ()); -/// } -/// ``` #[derive(Clone, Copy)] pub(crate) struct Pender(*mut ()); @@ -451,15 +436,31 @@ impl SyncExecutor { /// /// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks /// that "want to run"). -/// - You must supply a [`Pender`]. The executor will call it to notify you it has work -/// to do. You must arrange for `poll()` to be called as soon as possible. +/// - You must supply a pender function, as shown below. The executor will call it to notify you +/// it has work to do. You must arrange for `poll()` to be called as soon as possible. +/// - Enabling `arch-xx` features will define a pender function for you. This means that you +/// are limited to using the executors provided to you by the architecture/platform +/// implementation. If you need a different executor, you must not enable `arch-xx` features. /// -/// The [`Pender`] can be called from *any* context: any thread, any interrupt priority +/// The pender can be called from *any* context: any thread, any interrupt priority /// level, etc. It may be called synchronously from any `Executor` method call as well. /// You must deal with this correctly. /// /// In particular, you must NOT call `poll` directly from the pender callback, as this violates /// the requirement for `poll` to not be called reentrantly. +/// +/// The pender function must be exported with the name `__pender` and have the following signature: +/// +/// ```rust +/// #[export_name = "__pender"] +/// fn pender(context: *mut ()) { +/// // schedule `poll()` to be called +/// } +/// ``` +/// +/// The `context` argument is a piece of arbitrary data the executor will pass to the pender. +/// You can set the `context` when calling [`Executor::new()`]. You can use it to, for example, +/// differentiate between executors, or to pass a pointer to a callback that should be called. #[repr(transparent)] pub struct Executor { pub(crate) inner: SyncExecutor, @@ -474,9 +475,9 @@ impl Executor { /// Create a new executor. /// - /// When the executor has work to do, it will call the [`Pender`]. + /// When the executor has work to do, it will call the pender function and pass `context` to it. /// - /// See [`Executor`] docs for details on `Pender`. + /// See [`Executor`] docs for details on the pender. pub fn new(context: *mut ()) -> Self { Self { inner: SyncExecutor::new(Pender(context)), @@ -502,16 +503,16 @@ impl Executor { /// This loops over all tasks that are queued to be polled (i.e. they're /// freshly spawned or they've been woken). Other tasks are not polled. /// - /// You must call `poll` after receiving a call to the [`Pender`]. It is OK - /// to call `poll` even when not requested by the `Pender`, but it wastes + /// You must call `poll` after receiving a call to the pender. It is OK + /// to call `poll` even when not requested by the pender, but it wastes /// energy. /// /// # Safety /// /// You must NOT call `poll` reentrantly on the same executor. /// - /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you - /// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to + /// In particular, note that `poll` may call the pender synchronously. Therefore, you + /// must NOT directly call `poll()` from the pender callback. Instead, the callback has to /// somehow schedule for `poll()` to be called later, at a time you know for sure there's /// no `poll()` already running. pub unsafe fn poll(&'static self) { -- cgit From 0a73c84df0936facecb3e1a97cf6f4795d321b87 Mon Sep 17 00:00:00 2001 From: Dániel Buga Date: Mon, 21 Aug 2023 13:55:30 +0200 Subject: Make AvailableTask public, deduplicate --- embassy-executor/src/raw/mod.rs | 113 ++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 50 deletions(-) (limited to 'embassy-executor/src/raw') diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 7caa3302f..c1d82e18a 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,12 +200,56 @@ 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()); + + let task = TaskRef::new(self.task); + + SpawnToken::new(task) } - TaskRef::new(self.task) + } + + /// Initialize the [`TaskStorage`] to run the given future. + pub fn initialize(self, future: impl FnOnce() -> F) -> SpawnToken { + self.initialize_impl::(future) + } + + /// Initialize the [`TaskStorage`] to run the given future. + /// + /// # 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 __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`. + // + // The problem is this is more restrictive than needed. Once the future is executing, + // it is never sent to another thread. It is only sent when spawning. It should be + // enough for the task's arguments to be Send. (and in practice it's super easy to + // accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.) + // + // We can do it by sending the task args and constructing the future in the executor thread + // on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy + // of the args. + // + // Luckily, an `async fn` future contains just the args when freshly constructed. So, if the + // args are Send, it's OK to send a !Send future, as long as we do it before first polling it. + // + // (Note: this is how the generators are implemented today, it's not officially guaranteed yet, + // but it's possible it'll be guaranteed in the future. See zulip thread: + // https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures ) + // + // The `FutFn` captures all the args, so if it's Send, the task can be send-spawned. + // This is why we return `SpawnToken` below. + // + // 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) } } @@ -223,6 +268,13 @@ impl TaskPool { } } + 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. @@ -231,14 +283,7 @@ impl TaskPool { /// 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(), - } + self.spawn_impl::(future) } /// Like spawn(), but allows the task to be send-spawned if the args are Send even if @@ -254,40 +299,8 @@ impl TaskPool { where FutFn: FnOnce() -> F, { - // 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`. - // - // The problem is this is more restrictive than needed. Once the future is executing, - // it is never sent to another thread. It is only sent when spawning. It should be - // enough for the task's arguments to be Send. (and in practice it's super easy to - // accidentally make your futures !Send, for example by holding an `Rc` or a `&RefCell` across an `.await`.) - // - // We can do it by sending the task args and constructing the future in the executor thread - // on first poll. However, this cannot be done in-place, so it'll waste stack space for a copy - // of the args. - // - // Luckily, an `async fn` future contains just the args when freshly constructed. So, if the - // args are Send, it's OK to send a !Send future, as long as we do it before first polling it. - // - // (Note: this is how the generators are implemented today, it's not officially guaranteed yet, - // but it's possible it'll be guaranteed in the future. See zulip thread: - // https://rust-lang.zulipchat.com/#narrow/stream/187312-wg-async/topic/.22only.20before.20poll.22.20Send.20futures ) - // - // The `FutFn` captures all the args, so if it's Send, the task can be send-spawned. - // This is why we return `SpawnToken` below. - // - // 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`. - - 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(), - } + // See the comment in AvailableTask::__initialize_async_fn for explanation. + self.spawn_impl::(future) } } -- cgit