From 20822e8d6508b340710c3024036e7a5355b83f28 Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Wed, 9 Jul 2025 01:18:04 +0200 Subject: [PATCH] executor: add "task metadata" concept, make name a task metadata. --- .github/ci/test.sh | 2 +- ci.sh | 1 + embassy-executor/Cargo.toml | 7 +++- embassy-executor/src/lib.rs | 3 ++ embassy-executor/src/metadata.rs | 56 +++++++++++++++++++++++++- embassy-executor/src/raw/mod.rs | 13 ++++-- embassy-executor/src/raw/trace.rs | 29 +------------- embassy-executor/src/spawner.rs | 66 ++++++------------------------- embassy-executor/tests/test.rs | 31 +++++++++++++++ 9 files changed, 120 insertions(+), 88 deletions(-) diff --git a/.github/ci/test.sh b/.github/ci/test.sh index c9b332cf8..33dfa48c9 100755 --- a/.github/ci/test.sh +++ b/.github/ci/test.sh @@ -12,7 +12,7 @@ export CARGO_TARGET_DIR=/ci/cache/target # used when pointing stm32-metapac to a CI-built one. export CARGO_NET_GIT_FETCH_WITH_CLI=true -cargo test --manifest-path ./embassy-executor/Cargo.toml +cargo test --manifest-path ./embassy-executor/Cargo.toml --features metadata-name cargo test --manifest-path ./embassy-futures/Cargo.toml cargo test --manifest-path ./embassy-sync/Cargo.toml cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml diff --git a/ci.sh b/ci.sh index 4934c6d8e..a585a3ab8 100755 --- a/ci.sh +++ b/ci.sh @@ -31,6 +31,7 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,metadata-name \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,trace \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,rtos-trace \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,trace,rtos-trace \ diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 78c04517e..c43ac43a4 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -109,6 +109,11 @@ arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] ## spin (architecture agnostic; never sleeps) arch-spin = ["_arch"] +#! ### Metadata + +## Enable the `name` field in task metadata. +metadata-name = [] + #! ### Executor ## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) @@ -118,5 +123,5 @@ executor-interrupt = [] ## Enable tracing hooks trace = ["_any_trace"] ## Enable support for rtos-trace framework -rtos-trace = ["dep:rtos-trace", "_any_trace", "dep:embassy-time-driver"] +rtos-trace = ["_any_trace", "metadata-name", "dep:rtos-trace", "dep:embassy-time-driver"] _any_trace = [] diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index 0747db032..e47b8eb9f 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -54,6 +54,9 @@ pub mod raw; mod spawner; pub use spawner::*; +mod metadata; +pub use metadata::*; + /// Implementation details for embassy macros. /// Do not use. Used for macros and HALs only. Not covered by semver guarantees. #[doc(hidden)] diff --git a/embassy-executor/src/metadata.rs b/embassy-executor/src/metadata.rs index 957417f6b..f92c9b37c 100644 --- a/embassy-executor/src/metadata.rs +++ b/embassy-executor/src/metadata.rs @@ -1 +1,55 @@ -pub struct Metadata {} +#[cfg(feature = "metadata-name")] +use core::cell::Cell; +use core::future::{poll_fn, Future}; +use core::task::Poll; + +#[cfg(feature = "metadata-name")] +use critical_section::Mutex; + +use crate::raw; + +/// Metadata associated with a task. +pub struct Metadata { + #[cfg(feature = "metadata-name")] + name: Mutex>>, +} + +impl Metadata { + pub(crate) const fn new() -> Self { + Self { + #[cfg(feature = "metadata-name")] + name: Mutex::new(Cell::new(None)), + } + } + + pub(crate) fn reset(&self) { + #[cfg(feature = "metadata-name")] + critical_section::with(|cs| self.name.borrow(cs).set(None)); + } + + /// Get the metadata for the current task. + /// + /// You can use this to read or modify the current task's metadata. + /// + /// This function is `async` just to get access to the current async + /// context. It returns instantly, it does not block/yield. + pub fn for_current_task() -> impl Future { + poll_fn(|cx| Poll::Ready(raw::task_from_waker(cx.waker()).metadata())) + } + + /// Get this task's name + /// + /// NOTE: this takes a critical section. + #[cfg(feature = "metadata-name")] + pub fn name(&self) -> Option<&'static str> { + critical_section::with(|cs| self.name.borrow(cs).get()) + } + + /// Set this task's name + /// + /// NOTE: this takes a critical section. + #[cfg(feature = "metadata-name")] + pub fn set_name(&self, name: &'static str) { + critical_section::with(|cs| self.name.borrow(cs).set(Some(name))) + } +} diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 87328df5a..de9ef1eec 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -41,6 +41,7 @@ use self::state::State; use self::util::{SyncUnsafeCell, UninitCell}; pub use self::waker::task_from_waker; use super::SpawnToken; +use crate::Metadata; #[no_mangle] extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static mut TimerQueueItem { @@ -94,8 +95,8 @@ pub(crate) struct TaskHeader { /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. pub(crate) timer_queue_item: TimerQueueItem, - #[cfg(feature = "_any_trace")] - pub(crate) name: Option<&'static str>, + pub(crate) metadata: Metadata, + #[cfg(feature = "rtos-trace")] all_tasks_next: AtomicPtr, } @@ -127,6 +128,10 @@ impl TaskRef { unsafe { self.ptr.as_ref() } } + pub(crate) fn metadata(self) -> &'static Metadata { + unsafe { &self.ptr.as_ref().metadata } + } + /// Returns a reference to the executor that the task is currently running on. pub unsafe fn executor(self) -> Option<&'static Executor> { let executor = self.header().executor.load(Ordering::Relaxed); @@ -193,8 +198,7 @@ impl TaskStorage { poll_fn: SyncUnsafeCell::new(None), timer_queue_item: TimerQueueItem::new(), - #[cfg(feature = "_any_trace")] - name: None, + metadata: Metadata::new(), #[cfg(feature = "rtos-trace")] all_tasks_next: AtomicPtr::new(core::ptr::null_mut()), }, @@ -281,6 +285,7 @@ impl AvailableTask { fn initialize_impl(self, future: impl FnOnce() -> F) -> SpawnToken { unsafe { + self.task.raw.metadata.reset(); self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); self.task.future.write_in_place(future); diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs index 636608d02..ab0c1b8b6 100644 --- a/embassy-executor/src/raw/trace.rs +++ b/embassy-executor/src/raw/trace.rs @@ -168,32 +168,6 @@ impl TaskTracker { } } -/// Extension trait for `TaskRef` that provides tracing functionality. -/// -/// This trait is only available when the `trace` feature is enabled. -/// It extends `TaskRef` with methods for accessing and modifying task identifiers -/// and names, which are useful for debugging, logging, and performance analysis. -pub trait TaskRefTrace { - /// Get the name for a task - fn name(&self) -> Option<&'static str>; - - /// Set the name for a task - fn set_name(&self, name: Option<&'static str>); -} - -impl TaskRefTrace for TaskRef { - fn name(&self) -> Option<&'static str> { - self.header().name - } - - fn set_name(&self, name: Option<&'static str>) { - unsafe { - let header_ptr = self.ptr.as_ptr() as *mut TaskHeader; - (*header_ptr).name = name; - } - } -} - #[cfg(feature = "trace")] extern "Rust" { /// This callback is called when the executor begins polling. This will always @@ -383,9 +357,8 @@ where impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { fn task_list() { with_all_active_tasks(|task| { - let name = task.name().unwrap_or("unnamed task\0"); let info = rtos_trace::TaskInfo { - name, + name: task.metadata().name().unwrap_or("unnamed task\0"), priority: 0, stack_base: 0, stack_size: 0, diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 7550e8ea4..cd2113a28 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -5,8 +5,7 @@ use core::sync::atomic::Ordering; use core::task::Poll; use super::raw; -#[cfg(feature = "trace")] -use crate::raw::trace::TaskRefTrace; +use crate::Metadata; /// Token to spawn a newly-created task in an executor. /// @@ -36,6 +35,14 @@ impl SpawnToken { } } + /// Return a SpawnToken that represents a failed spawn. + pub fn new_failed() -> Self { + Self { + raw_task: None, + phantom: PhantomData, + } + } + /// Returns the task ID if available, otherwise 0 /// This can be used in combination with rtos-trace to match task names with IDs pub fn id(&self) -> u32 { @@ -45,12 +52,10 @@ impl SpawnToken { } } - /// Return a SpawnToken that represents a failed spawn. - pub fn new_failed() -> Self { - Self { - raw_task: None, - phantom: PhantomData, - } + /// Get the metadata for this task. You can use this to set metadata fields + /// prior to spawning it. + pub fn metadata(&self) -> &Metadata { + self.raw_task.unwrap().metadata() } } @@ -198,51 +203,6 @@ impl Spawner { } } -/// Extension trait adding tracing capabilities to the Spawner -/// -/// This trait provides an additional method to spawn tasks with an associated name, -/// which can be useful for debugging and tracing purposes. -pub trait SpawnerTraceExt { - /// Spawns a new task with a specified name. - /// - /// # Arguments - /// * `name` - Static string name to associate with the task - /// * `token` - Token representing the task to spawn - /// - /// # Returns - /// Result indicating whether the spawn was successful - fn spawn_named(&self, name: &'static str, token: SpawnToken) -> Result<(), SpawnError>; -} - -/// Implementation of the SpawnerTraceExt trait for Spawner when trace is enabled -#[cfg(feature = "trace")] -impl SpawnerTraceExt for Spawner { - fn spawn_named(&self, name: &'static str, token: SpawnToken) -> Result<(), SpawnError> { - let task = token.raw_task; - core::mem::forget(token); - - match task { - Some(task) => { - // Set the name when trace is enabled - task.set_name(Some(name)); - - unsafe { self.executor.spawn(task) }; - Ok(()) - } - None => Err(SpawnError::Busy), - } - } -} - -/// Implementation of the SpawnerTraceExt trait for Spawner when trace is disabled -#[cfg(not(feature = "trace"))] -impl SpawnerTraceExt for Spawner { - fn spawn_named(&self, _name: &'static str, token: SpawnToken) -> Result<(), SpawnError> { - // When trace is disabled, just forward to regular spawn and ignore the name - self.spawn(token) - } -} - /// Handle to spawn tasks into an executor from any thread. /// /// This Spawner can be used from any thread (it is Send), but it can diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs index b84d3785a..530314ac3 100644 --- a/embassy-executor/tests/test.rs +++ b/embassy-executor/tests/test.rs @@ -326,3 +326,34 @@ fn recursive_task() { spawner.spawn(task1()); } } + +#[cfg(feature = "metadata-name")] +#[test] +fn task_metadata() { + #[task] + async fn task1(expected_name: Option<&'static str>) { + use embassy_executor::Metadata; + assert_eq!(Metadata::for_current_task().await.name(), expected_name); + } + + // check no task name + let (executor, _) = setup(); + executor.spawner().spawn(task1(None)).unwrap(); + unsafe { executor.poll() }; + + // check setting task name + let token = task1(Some("foo")); + token.metadata().set_name("foo"); + executor.spawner().spawn(token).unwrap(); + unsafe { executor.poll() }; + + let token = task1(Some("bar")); + token.metadata().set_name("bar"); + executor.spawner().spawn(token).unwrap(); + unsafe { executor.poll() }; + + // check name is cleared if the task pool slot is recycled. + let (executor, _) = setup(); + executor.spawner().spawn(task1(None)).unwrap(); + unsafe { executor.poll() }; +}