aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor
diff options
context:
space:
mode:
author1-rafael-1 <[email protected]>2025-09-15 20:07:18 +0200
committer1-rafael-1 <[email protected]>2025-09-15 20:07:18 +0200
commit6bb3d2c0720fa082f27d3cdb70f516058497ec87 (patch)
tree5a1e255cff999b00800f203b91a759c720c973e5 /embassy-executor
parenteb685574601d98c44faed9a3534d056199b46e20 (diff)
parent92a6fd2946f2cbb15359290f68aa360953da2ff7 (diff)
Merge branch 'main' into rp2040-rtc-alarm
Diffstat (limited to 'embassy-executor')
-rw-r--r--embassy-executor/CHANGELOG.md37
-rw-r--r--embassy-executor/Cargo.toml116
-rw-r--r--embassy-executor/build_common.rs32
-rw-r--r--embassy-executor/src/arch/cortex_ar.rs84
-rw-r--r--embassy-executor/src/lib.rs56
-rw-r--r--embassy-executor/src/metadata.rs148
-rw-r--r--embassy-executor/src/raw/deadline.rs44
-rw-r--r--embassy-executor/src/raw/mod.rs113
-rw-r--r--embassy-executor/src/raw/run_queue.rs213
-rw-r--r--embassy-executor/src/raw/run_queue_atomics.rs88
-rw-r--r--embassy-executor/src/raw/run_queue_critical_section.rs74
-rw-r--r--embassy-executor/src/raw/state_atomics.rs21
-rw-r--r--embassy-executor/src/raw/state_critical_section.rs15
-rw-r--r--embassy-executor/src/raw/timer_queue.rs73
-rw-r--r--embassy-executor/src/raw/trace.rs173
-rw-r--r--embassy-executor/src/spawner.rs88
-rw-r--r--embassy-executor/tests/test.rs93
-rw-r--r--embassy-executor/tests/ui.rs14
-rw-r--r--embassy-executor/tests/ui/bad_return_impl_future.rs9
-rw-r--r--embassy-executor/tests/ui/bad_return_impl_future.stderr120
-rw-r--r--embassy-executor/tests/ui/bad_return_impl_future_nightly.rs9
-rw-r--r--embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr10
-rw-r--r--embassy-executor/tests/ui/nonstatic_struct_elided.stderr14
-rw-r--r--embassy-executor/tests/ui/return_impl_future_nonsend.rs21
-rw-r--r--embassy-executor/tests/ui/return_impl_future_nonsend.stderr17
-rw-r--r--embassy-executor/tests/ui/return_impl_send.rs6
-rw-r--r--embassy-executor/tests/ui/return_impl_send.stderr137
-rw-r--r--embassy-executor/tests/ui/return_impl_send_nightly.rs6
-rw-r--r--embassy-executor/tests/ui/return_impl_send_nightly.stderr10
-rw-r--r--embassy-executor/tests/ui/spawn_nonsend.rs16
-rw-r--r--embassy-executor/tests/ui/spawn_nonsend.stderr41
-rw-r--r--embassy-executor/tests/ui/task_safety_attribute.rs25
-rw-r--r--embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs10
-rw-r--r--embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr18
34 files changed, 1524 insertions, 427 deletions
diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md
index 5d2468c58..6f079a11a 100644
--- a/embassy-executor/CHANGELOG.md
+++ b/embassy-executor/CHANGELOG.md
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
5The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 7
8<!-- next-header -->
9## Unreleased - ReleaseDate
10
11- Added new metadata API for tasks.
12- Main task automatically gets a name of `main` when the `metadata-name` feature is enabled.
13- Upgraded rtos-trace
14- Added optional "highest priority" scheduling
15- Added optional "earliest deadline first" EDF scheduling
16
17## 0.9.1 - 2025-08-31
18
19- Fixed performance regression on some ESP32 MCUs.
20
21## 0.9.0 - 2025-08-26
22
23- Added `extern "Rust" fn __embassy_time_queue_item_from_waker`
24- Removed `TaskRef::dangling`
25- Added `embassy-executor-timer-queue` as a dependency
26- Moved the `TimeQueueItem` struct and `timer-item-payload-size-*` features (as `timer-item-size-X-words`) into `embassy-executor-timer-queue`
27
28## 0.8.0 - 2025-07-31
29
30- Added `SpawnToken::id`
31- Task pools are now statically allocated on stable rust. All `task-arena-size-*` features have been removed and are no longer necessary.
32- New trace hooks: `_embassy_trace_poll_start` & `_embassy_trace_task_end`
33- Added task naming capability to tracing infrastructure
34- Added `Executor::id` & `Spawner::executor_id`
35- Disable `critical-section/std` for arch-std
36- Added possibility to select an executor in `#[embassy_executor::main]`
37- Fix AVR executor
38- executor: Make state implementations and their conditions match
39- Added support for Cortex-A and Cortex-R
40- Added support for `-> impl Future<Output = ()>` in `#[task]`
41- Fixed `Send` unsoundness with `-> impl Future` tasks
42- Marked `Spawner::for_current_executor` as `unsafe`
43- `#[task]` now properly marks the generated function as unsafe if the task is marked unsafe
44
8## 0.7.0 - 2025-01-02 45## 0.7.0 - 2025-01-02
9 46
10- Performance optimizations. 47- Performance optimizations.
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index 0f69d3c8a..f6dce5c0e 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -1,6 +1,6 @@
1[package] 1[package]
2name = "embassy-executor" 2name = "embassy-executor"
3version = "0.7.0" 3version = "0.9.1"
4edition = "2021" 4edition = "2021"
5license = "MIT OR Apache-2.0" 5license = "MIT OR Apache-2.0"
6description = "async/await executor designed for embedded usage" 6description = "async/await executor designed for embedded usage"
@@ -12,10 +12,61 @@ categories = [
12 "asynchronous", 12 "asynchronous",
13] 13]
14 14
15[package.metadata.embassy]
16build = [
17 {target = "thumbv7em-none-eabi", features = []},
18 {target = "thumbv7em-none-eabi", features = ["log"]},
19 {target = "thumbv7em-none-eabi", features = ["defmt"]},
20 {target = "thumbv6m-none-eabi", features = ["defmt"]},
21 {target = "thumbv6m-none-eabi", features = ["arch-cortex-m", "defmt", "executor-interrupt", "executor-thread"]},
22 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m"]},
23 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "rtos-trace"]},
24 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread"]},
25 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt"]},
26 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread"]},
27 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver"]},
28 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority"]},
29 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-priority", "scheduler-deadline"]},
30 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "embassy-time-driver", "scheduler-deadline"]},
31 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "scheduler-priority", "scheduler-deadline"]},
32 {target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-interrupt", "executor-thread", "scheduler-deadline"]},
33 {target = "armv7a-none-eabi", features = ["arch-cortex-ar", "executor-thread"]},
34 {target = "armv7r-none-eabi", features = ["arch-cortex-ar", "executor-thread"]},
35 {target = "armv7r-none-eabihf", features = ["arch-cortex-ar", "executor-thread"]},
36 {target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32"]},
37 {target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"]},
38 # Nightly builds
39 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly"]},
40 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "log"]},
41 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "defmt"]},
42 {group = "nightly", target = "thumbv6m-none-eabi", features = ["nightly", "defmt"]},
43 {group = "nightly", target = "thumbv6m-none-eabi", features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]},
44 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "arch-cortex-m"]},
45 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "arch-cortex-m", "executor-thread"]},
46 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "arch-cortex-m", "executor-interrupt"]},
47 {group = "nightly", target = "thumbv7em-none-eabi", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt"]},
48 {group = "nightly", target = "riscv32imac-unknown-none-elf", features = ["nightly", "arch-riscv32"]},
49 {group = "nightly", target = "riscv32imac-unknown-none-elf", features = ["nightly", "arch-riscv32", "executor-thread"]},
50 {group = "nightly", target = "armv7a-none-eabi", features = ["nightly", "arch-cortex-ar", "executor-thread"]},
51 {group = "nightly", target = "avr-none", features = ["nightly", "arch-avr", "avr-device/atmega328p"], build-std = ["core", "alloc"], env = { RUSTFLAGS = "-Ctarget-cpu=atmega328p" }},
52 # Xtensa builds
53 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = []},
54 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["log"]},
55 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt"]},
56 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s2-none-elf", features = ["defmt"]},
57 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["defmt", "arch-spin", "executor-thread"]},
58 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s2-none-elf", features = ["defmt", "arch-spin", "executor-thread"]},
59 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32s3-none-elf", features = ["defmt", "arch-spin", "executor-thread"]},
60 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["arch-spin"]},
61 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["arch-spin", "rtos-trace"]},
62 {group = "xtensa", build-std = ["core", "alloc"], target = "xtensa-esp32-none-elf", features = ["arch-spin", "executor-thread"]},
63]
64
65
15[package.metadata.embassy_docs] 66[package.metadata.embassy_docs]
16src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" 67src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/"
17src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" 68src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/"
18features = ["defmt"] 69features = ["defmt", "scheduler-deadline", "scheduler-priority"]
19flavors = [ 70flavors = [
20 { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, 71 { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] },
21 { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, 72 { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] },
@@ -26,15 +77,16 @@ flavors = [
26[package.metadata.docs.rs] 77[package.metadata.docs.rs]
27default-target = "thumbv7em-none-eabi" 78default-target = "thumbv7em-none-eabi"
28targets = ["thumbv7em-none-eabi"] 79targets = ["thumbv7em-none-eabi"]
29features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] 80features = ["defmt", "arch-cortex-m", "executor-thread", "executor-interrupt", "scheduler-deadline", "scheduler-priority", "embassy-time-driver"]
30 81
31[dependencies] 82[dependencies]
32defmt = { version = "0.3", optional = true } 83defmt = { version = "1.0.1", optional = true }
33log = { version = "0.4.14", optional = true } 84log = { version = "0.4.14", optional = true }
34rtos-trace = { version = "0.1.3", optional = true } 85rtos-trace = { version = "0.2", optional = true }
35 86
36embassy-executor-macros = { version = "0.6.2", path = "../embassy-executor-macros" } 87embassy-executor-macros = { version = "0.7.0", path = "../embassy-executor-macros" }
37embassy-time-driver = { version = "0.2", path = "../embassy-time-driver", optional = true } 88embassy-time-driver = { version = "0.2.1", path = "../embassy-time-driver", optional = true }
89embassy-executor-timer-queue = { version = "0.1", path = "../embassy-executor-timer-queue" }
38critical-section = "1.1" 90critical-section = "1.1"
39 91
40document-features = "0.2.7" 92document-features = "0.2.7"
@@ -45,6 +97,9 @@ portable-atomic = { version = "1.5", optional = true }
45# arch-cortex-m dependencies 97# arch-cortex-m dependencies
46cortex-m = { version = "0.7.6", optional = true } 98cortex-m = { version = "0.7.6", optional = true }
47 99
100# arch-cortex-ar dependencies
101cortex-ar = { version = "0.2", optional = true }
102
48# arch-wasm dependencies 103# arch-wasm dependencies
49wasm-bindgen = { version = "0.2.82", optional = true } 104wasm-bindgen = { version = "0.2.82", optional = true }
50js-sys = { version = "0.3", optional = true } 105js-sys = { version = "0.3", optional = true }
@@ -52,10 +107,16 @@ js-sys = { version = "0.3", optional = true }
52# arch-avr dependencies 107# arch-avr dependencies
53avr-device = { version = "0.7.0", optional = true } 108avr-device = { version = "0.7.0", optional = true }
54 109
110
111[dependencies.cordyceps]
112version = "0.3.4"
113features = ["no-cache-pad"]
114
55[dev-dependencies] 115[dev-dependencies]
56critical-section = { version = "1.1", features = ["std"] } 116critical-section = { version = "1.1", features = ["std"] }
57trybuild = "1.0" 117trybuild = "1.0"
58embassy-sync = { path = "../embassy-sync" } 118embassy-sync = { path = "../embassy-sync" }
119rustversion = "1.0.21"
59 120
60[features] 121[features]
61 122
@@ -73,6 +134,8 @@ _arch = [] # some arch was picked
73arch-std = ["_arch"] 134arch-std = ["_arch"]
74## Cortex-M 135## Cortex-M
75arch-cortex-m = ["_arch", "dep:cortex-m"] 136arch-cortex-m = ["_arch", "dep:cortex-m"]
137## Cortex-A/R
138arch-cortex-ar = ["_arch", "dep:cortex-ar"]
76## RISC-V 32 139## RISC-V 32
77arch-riscv32 = ["_arch"] 140arch-riscv32 = ["_arch"]
78## WASM 141## WASM
@@ -82,29 +145,30 @@ arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"]
82## spin (architecture agnostic; never sleeps) 145## spin (architecture agnostic; never sleeps)
83arch-spin = ["_arch"] 146arch-spin = ["_arch"]
84 147
148#! ### Metadata
149
150## Enable the `name` field in task metadata.
151metadata-name = ["embassy-executor-macros/metadata-name"]
152
85#! ### Executor 153#! ### Executor
86 154
87## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) 155## Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs)
88executor-thread = [] 156executor-thread = []
89## Enable the interrupt-mode executor (available in Cortex-M only) 157## Enable the interrupt-mode executor (available in Cortex-M only)
90executor-interrupt = [] 158executor-interrupt = []
91## Enable tracing support (adds some overhead) 159## Enable tracing hooks
92trace = [] 160trace = ["_any_trace"]
93## Enable support for rtos-trace framework 161## Enable support for rtos-trace framework
94rtos-trace = ["dep:rtos-trace", "trace", "dep:embassy-time-driver"] 162rtos-trace = ["_any_trace", "metadata-name", "dep:rtos-trace", "embassy-time-driver"]
95 163_any_trace = []
96#! ### Timer Item Payload Size 164
97#! Sets the size of the payload for timer items, allowing integrated timer implementors to store 165## Enable "Earliest Deadline First" Scheduler, using soft-realtime "deadlines" to prioritize
98#! additional data in the timer item. The payload field will be aligned to this value as well. 166## tasks based on the remaining time before their deadline. Adds some overhead.
99#! If these features are not defined, the timer item will contain no payload field. 167scheduler-deadline = []
100 168
101_timer-item-payload = [] # A size was picked 169## Enable "Highest Priority First" Scheduler. Adds some overhead.
102 170scheduler-priority = []
103## 1 bytes 171
104timer-item-payload-size-1 = ["_timer-item-payload"] 172## Enable the embassy_time_driver dependency.
105## 2 bytes 173## This can unlock extra APIs, for example for the `sheduler-deadline`
106timer-item-payload-size-2 = ["_timer-item-payload"] 174embassy-time-driver = ["dep:embassy-time-driver"]
107## 4 bytes
108timer-item-payload-size-4 = ["_timer-item-payload"]
109## 8 bytes
110timer-item-payload-size-8 = ["_timer-item-payload"]
diff --git a/embassy-executor/build_common.rs b/embassy-executor/build_common.rs
index b15a8369f..4f24e6d37 100644
--- a/embassy-executor/build_common.rs
+++ b/embassy-executor/build_common.rs
@@ -92,35 +92,3 @@ pub fn set_target_cfgs(cfgs: &mut CfgSet) {
92 92
93 cfgs.set("has_fpu", target.ends_with("-eabihf")); 93 cfgs.set("has_fpu", target.ends_with("-eabihf"));
94} 94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
97pub struct CompilerDate {
98 year: u16,
99 month: u8,
100 day: u8,
101}
102
103impl CompilerDate {
104 fn parse(date: &str) -> Option<Self> {
105 let mut parts = date.split('-');
106 let year = parts.next()?.parse().ok()?;
107 let month = parts.next()?.parse().ok()?;
108 let day = parts.next()?.parse().ok()?;
109 Some(Self { year, month, day })
110 }
111}
112
113impl PartialEq<&str> for CompilerDate {
114 fn eq(&self, other: &&str) -> bool {
115 let Some(other) = Self::parse(other) else {
116 return false;
117 };
118 self.eq(&other)
119 }
120}
121
122impl PartialOrd<&str> for CompilerDate {
123 fn partial_cmp(&self, other: &&str) -> Option<std::cmp::Ordering> {
124 Self::parse(other).map(|other| self.cmp(&other))
125 }
126}
diff --git a/embassy-executor/src/arch/cortex_ar.rs b/embassy-executor/src/arch/cortex_ar.rs
new file mode 100644
index 000000000..f9e2f3f7c
--- /dev/null
+++ b/embassy-executor/src/arch/cortex_ar.rs
@@ -0,0 +1,84 @@
1#[cfg(feature = "executor-interrupt")]
2compile_error!("`executor-interrupt` is not supported with `arch-cortex-ar`.");
3
4#[export_name = "__pender"]
5#[cfg(any(feature = "executor-thread", feature = "executor-interrupt"))]
6fn __pender(context: *mut ()) {
7 // `context` is always `usize::MAX` created by `Executor::run`.
8 let context = context as usize;
9
10 #[cfg(feature = "executor-thread")]
11 // Try to make Rust optimize the branching away if we only use thread mode.
12 if !cfg!(feature = "executor-interrupt") || context == THREAD_PENDER {
13 cortex_ar::asm::sev();
14 return;
15 }
16}
17
18#[cfg(feature = "executor-thread")]
19pub use thread::*;
20#[cfg(feature = "executor-thread")]
21mod thread {
22 pub(super) const THREAD_PENDER: usize = usize::MAX;
23
24 use core::marker::PhantomData;
25
26 use cortex_ar::asm::wfe;
27 pub use embassy_executor_macros::main_cortex_ar as main;
28
29 use crate::{raw, Spawner};
30
31 /// Thread mode executor, using WFE/SEV.
32 ///
33 /// This is the simplest and most common kind of executor. It runs on
34 /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction
35 /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction
36 /// is executed, to make the `WFE` exit from sleep and poll the task.
37 ///
38 /// This executor allows for ultra low power consumption for chips where `WFE`
39 /// triggers low-power sleep without extra steps. If your chip requires extra steps,
40 /// you may use [`raw::Executor`] directly to program custom behavior.
41 pub struct Executor {
42 inner: raw::Executor,
43 not_send: PhantomData<*mut ()>,
44 }
45
46 impl Executor {
47 /// Create a new Executor.
48 pub fn new() -> Self {
49 Self {
50 inner: raw::Executor::new(THREAD_PENDER as *mut ()),
51 not_send: PhantomData,
52 }
53 }
54
55 /// Run the executor.
56 ///
57 /// The `init` closure is called with a [`Spawner`] that spawns tasks on
58 /// this executor. Use it to spawn the initial task(s). After `init` returns,
59 /// the executor starts running the tasks.
60 ///
61 /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`),
62 /// for example by passing it as an argument to the initial tasks.
63 ///
64 /// This function requires `&'static mut self`. This means you have to store the
65 /// Executor instance in a place where it'll live forever and grants you mutable
66 /// access. There's a few ways to do this:
67 ///
68 /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe)
69 /// - a `static mut` (unsafe)
70 /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe)
71 ///
72 /// This function never returns.
73 pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! {
74 init(self.inner.spawner());
75
76 loop {
77 unsafe {
78 self.inner.poll();
79 }
80 wfe();
81 }
82 }
83 }
84}
diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs
index d6fd3d651..e47b8eb9f 100644
--- a/embassy-executor/src/lib.rs
+++ b/embassy-executor/src/lib.rs
@@ -26,6 +26,7 @@ macro_rules! check_at_most_one {
26check_at_most_one!( 26check_at_most_one!(
27 "arch-avr", 27 "arch-avr",
28 "arch-cortex-m", 28 "arch-cortex-m",
29 "arch-cortex-ar",
29 "arch-riscv32", 30 "arch-riscv32",
30 "arch-std", 31 "arch-std",
31 "arch-wasm", 32 "arch-wasm",
@@ -35,6 +36,7 @@ check_at_most_one!(
35#[cfg(feature = "_arch")] 36#[cfg(feature = "_arch")]
36#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] 37#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")]
37#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] 38#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")]
39#[cfg_attr(feature = "arch-cortex-ar", path = "arch/cortex_ar.rs")]
38#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] 40#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")]
39#[cfg_attr(feature = "arch-std", path = "arch/std.rs")] 41#[cfg_attr(feature = "arch-std", path = "arch/std.rs")]
40#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] 42#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")]
@@ -52,6 +54,9 @@ pub mod raw;
52mod spawner; 54mod spawner;
53pub use spawner::*; 55pub use spawner::*;
54 56
57mod metadata;
58pub use metadata::*;
59
55/// Implementation details for embassy macros. 60/// Implementation details for embassy macros.
56/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. 61/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
57#[doc(hidden)] 62#[doc(hidden)]
@@ -63,8 +68,17 @@ pub mod _export {
63 68
64 use crate::raw::TaskPool; 69 use crate::raw::TaskPool;
65 70
71 trait TaskReturnValue {}
72 impl TaskReturnValue for () {}
73 impl TaskReturnValue for Never {}
74
75 #[diagnostic::on_unimplemented(
76 message = "task futures must resolve to `()` or `!`",
77 note = "use `async fn` or change the return type to `impl Future<Output = ()>`"
78 )]
79 #[allow(private_bounds)]
66 pub trait TaskFn<Args>: Copy { 80 pub trait TaskFn<Args>: Copy {
67 type Fut: Future + 'static; 81 type Fut: Future<Output: TaskReturnValue> + 'static;
68 } 82 }
69 83
70 macro_rules! task_fn_impl { 84 macro_rules! task_fn_impl {
@@ -72,7 +86,7 @@ pub mod _export {
72 impl<F, Fut, $($Tn,)*> TaskFn<($($Tn,)*)> for F 86 impl<F, Fut, $($Tn,)*> TaskFn<($($Tn,)*)> for F
73 where 87 where
74 F: Copy + FnOnce($($Tn,)*) -> Fut, 88 F: Copy + FnOnce($($Tn,)*) -> Fut,
75 Fut: Future + 'static, 89 Fut: Future<Output: TaskReturnValue> + 'static,
76 { 90 {
77 type Fut = Fut; 91 type Fut = Fut;
78 } 92 }
@@ -203,4 +217,42 @@ pub mod _export {
203 Align268435456: 268435456, 217 Align268435456: 268435456,
204 Align536870912: 536870912, 218 Align536870912: 536870912,
205 ); 219 );
220
221 #[allow(dead_code)]
222 pub trait HasOutput {
223 type Output;
224 }
225
226 impl<O> HasOutput for fn() -> O {
227 type Output = O;
228 }
229
230 #[allow(dead_code)]
231 pub type Never = <fn() -> ! as HasOutput>::Output;
232}
233
234/// Implementation details for embassy macros.
235/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
236#[doc(hidden)]
237#[cfg(feature = "nightly")]
238pub mod _export {
239 #[diagnostic::on_unimplemented(
240 message = "task futures must resolve to `()` or `!`",
241 note = "use `async fn` or change the return type to `impl Future<Output = ()>`"
242 )]
243 pub trait TaskReturnValue {}
244 impl TaskReturnValue for () {}
245 impl TaskReturnValue for Never {}
246
247 #[allow(dead_code)]
248 pub trait HasOutput {
249 type Output;
250 }
251
252 impl<O> HasOutput for fn() -> O {
253 type Output = O;
254 }
255
256 #[allow(dead_code)]
257 pub type Never = <fn() -> ! as HasOutput>::Output;
206} 258}
diff --git a/embassy-executor/src/metadata.rs b/embassy-executor/src/metadata.rs
new file mode 100644
index 000000000..bc0df0f83
--- /dev/null
+++ b/embassy-executor/src/metadata.rs
@@ -0,0 +1,148 @@
1#[cfg(feature = "metadata-name")]
2use core::cell::Cell;
3use core::future::{poll_fn, Future};
4#[cfg(feature = "scheduler-priority")]
5use core::sync::atomic::{AtomicU8, Ordering};
6use core::task::Poll;
7
8#[cfg(feature = "metadata-name")]
9use critical_section::Mutex;
10
11use crate::raw;
12#[cfg(feature = "scheduler-deadline")]
13use crate::raw::Deadline;
14
15/// Metadata associated with a task.
16pub struct Metadata {
17 #[cfg(feature = "metadata-name")]
18 name: Mutex<Cell<Option<&'static str>>>,
19 #[cfg(feature = "scheduler-priority")]
20 priority: AtomicU8,
21 #[cfg(feature = "scheduler-deadline")]
22 deadline: raw::Deadline,
23}
24
25impl Metadata {
26 pub(crate) const fn new() -> Self {
27 Self {
28 #[cfg(feature = "metadata-name")]
29 name: Mutex::new(Cell::new(None)),
30 #[cfg(feature = "scheduler-priority")]
31 priority: AtomicU8::new(0),
32 // NOTE: The deadline is set to zero to allow the initializer to reside in `.bss`. This
33 // will be lazily initalized in `initialize_impl`
34 #[cfg(feature = "scheduler-deadline")]
35 deadline: raw::Deadline::new_unset(),
36 }
37 }
38
39 pub(crate) fn reset(&self) {
40 #[cfg(feature = "metadata-name")]
41 critical_section::with(|cs| self.name.borrow(cs).set(None));
42
43 #[cfg(feature = "scheduler-priority")]
44 self.set_priority(0);
45
46 // By default, deadlines are set to the maximum value, so that any task WITH
47 // a set deadline will ALWAYS be scheduled BEFORE a task WITHOUT a set deadline
48 #[cfg(feature = "scheduler-deadline")]
49 self.unset_deadline();
50 }
51
52 /// Get the metadata for the current task.
53 ///
54 /// You can use this to read or modify the current task's metadata.
55 ///
56 /// This function is `async` just to get access to the current async
57 /// context. It returns instantly, it does not block/yield.
58 pub fn for_current_task() -> impl Future<Output = &'static Self> {
59 poll_fn(|cx| Poll::Ready(raw::task_from_waker(cx.waker()).metadata()))
60 }
61
62 /// Get this task's name
63 ///
64 /// NOTE: this takes a critical section.
65 #[cfg(feature = "metadata-name")]
66 pub fn name(&self) -> Option<&'static str> {
67 critical_section::with(|cs| self.name.borrow(cs).get())
68 }
69
70 /// Set this task's name
71 ///
72 /// NOTE: this takes a critical section.
73 #[cfg(feature = "metadata-name")]
74 pub fn set_name(&self, name: &'static str) {
75 critical_section::with(|cs| self.name.borrow(cs).set(Some(name)))
76 }
77
78 /// Get this task's priority.
79 #[cfg(feature = "scheduler-priority")]
80 pub fn priority(&self) -> u8 {
81 self.priority.load(Ordering::Relaxed)
82 }
83
84 /// Set this task's priority.
85 #[cfg(feature = "scheduler-priority")]
86 pub fn set_priority(&self, priority: u8) {
87 self.priority.store(priority, Ordering::Relaxed)
88 }
89
90 /// Get this task's deadline.
91 #[cfg(feature = "scheduler-deadline")]
92 pub fn deadline(&self) -> u64 {
93 self.deadline.instant_ticks()
94 }
95
96 /// Set this task's deadline.
97 ///
98 /// This method does NOT check whether the deadline has already passed.
99 #[cfg(feature = "scheduler-deadline")]
100 pub fn set_deadline(&self, instant_ticks: u64) {
101 self.deadline.set(instant_ticks);
102 }
103
104 /// Remove this task's deadline.
105 /// This brings it back to the defaul where it's not scheduled ahead of other tasks.
106 #[cfg(feature = "scheduler-deadline")]
107 pub fn unset_deadline(&self) {
108 self.deadline.set(Deadline::UNSET_TICKS);
109 }
110
111 /// Set this task's deadline `duration_ticks` in the future from when
112 /// this future is polled. This deadline is saturated to the max tick value.
113 ///
114 /// Analogous to `Timer::after`.
115 #[cfg(all(feature = "scheduler-deadline", feature = "embassy-time-driver"))]
116 pub fn set_deadline_after(&self, duration_ticks: u64) {
117 let now = embassy_time_driver::now();
118
119 // Since ticks is a u64, saturating add is PROBABLY overly cautious, leave
120 // it for now, we can probably make this wrapping_add for performance
121 // reasons later.
122 let deadline = now.saturating_add(duration_ticks);
123
124 self.set_deadline(deadline);
125 }
126
127 /// Set the this task's deadline `increment_ticks` from the previous deadline.
128 ///
129 /// This deadline is saturated to the max tick value.
130 ///
131 /// Note that by default (unless otherwise set), tasks start life with the deadline
132 /// not set, which means this method will have no effect.
133 ///
134 /// Analogous to one increment of `Ticker::every().next()`.
135 ///
136 /// Returns the deadline that was set.
137 #[cfg(feature = "scheduler-deadline")]
138 pub fn increment_deadline(&self, duration_ticks: u64) {
139 let last = self.deadline();
140
141 // Since ticks is a u64, saturating add is PROBABLY overly cautious, leave
142 // it for now, we can probably make this wrapping_add for performance
143 // reasons later.
144 let deadline = last.saturating_add(duration_ticks);
145
146 self.set_deadline(deadline);
147 }
148}
diff --git a/embassy-executor/src/raw/deadline.rs b/embassy-executor/src/raw/deadline.rs
new file mode 100644
index 000000000..cc89fadb0
--- /dev/null
+++ b/embassy-executor/src/raw/deadline.rs
@@ -0,0 +1,44 @@
1use core::sync::atomic::{AtomicU32, Ordering};
2
3/// A type for interacting with the deadline of the current task
4///
5/// Requires the `scheduler-deadline` feature.
6///
7/// Note: Interacting with the deadline should be done locally in a task.
8/// In theory you could try to set or read the deadline from another task,
9/// but that will result in weird (though not unsound) behavior.
10pub(crate) struct Deadline {
11 instant_ticks_hi: AtomicU32,
12 instant_ticks_lo: AtomicU32,
13}
14
15impl Deadline {
16 pub(crate) const fn new(instant_ticks: u64) -> Self {
17 Self {
18 instant_ticks_hi: AtomicU32::new((instant_ticks >> 32) as u32),
19 instant_ticks_lo: AtomicU32::new(instant_ticks as u32),
20 }
21 }
22
23 pub(crate) const fn new_unset() -> Self {
24 Self::new(Self::UNSET_TICKS)
25 }
26
27 pub(crate) fn set(&self, instant_ticks: u64) {
28 self.instant_ticks_hi
29 .store((instant_ticks >> 32) as u32, Ordering::Relaxed);
30 self.instant_ticks_lo.store(instant_ticks as u32, Ordering::Relaxed);
31 }
32
33 /// Deadline value in ticks, same time base and ticks as `embassy-time`
34 pub(crate) fn instant_ticks(&self) -> u64 {
35 let hi = self.instant_ticks_hi.load(Ordering::Relaxed) as u64;
36 let lo = self.instant_ticks_lo.load(Ordering::Relaxed) as u64;
37
38 (hi << 32) | lo
39 }
40
41 /// Sentinel value representing an "unset" deadline, which has lower priority
42 /// than any other set deadline value
43 pub(crate) const UNSET_TICKS: u64 = u64::MAX;
44}
diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs
index 88d839e07..9f36c60bc 100644
--- a/embassy-executor/src/raw/mod.rs
+++ b/embassy-executor/src/raw/mod.rs
@@ -7,22 +7,28 @@
7//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe 7//! Using this module requires respecting subtle safety contracts. If you can, prefer using the safe
8//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe. 8//! [executor wrappers](crate::Executor) and the [`embassy_executor::task`](embassy_executor_macros::task) macro, which are fully safe.
9 9
10#[cfg_attr(target_has_atomic = "ptr", path = "run_queue_atomics.rs")]
11#[cfg_attr(not(target_has_atomic = "ptr"), path = "run_queue_critical_section.rs")]
12mod run_queue; 10mod run_queue;
13 11
14#[cfg_attr(all(cortex_m, target_has_atomic = "8"), path = "state_atomics_arm.rs")] 12#[cfg_attr(all(cortex_m, target_has_atomic = "32"), path = "state_atomics_arm.rs")]
15#[cfg_attr(all(not(cortex_m), target_has_atomic = "8"), path = "state_atomics.rs")] 13#[cfg_attr(
16#[cfg_attr(not(target_has_atomic = "8"), path = "state_critical_section.rs")] 14 all(not(cortex_m), any(target_has_atomic = "8", target_has_atomic = "32")),
15 path = "state_atomics.rs"
16)]
17#[cfg_attr(
18 not(any(target_has_atomic = "8", target_has_atomic = "32")),
19 path = "state_critical_section.rs"
20)]
17mod state; 21mod state;
18 22
19pub mod timer_queue; 23#[cfg(feature = "_any_trace")]
20#[cfg(feature = "trace")] 24pub mod trace;
21mod trace;
22pub(crate) mod util; 25pub(crate) mod util;
23#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] 26#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")]
24mod waker; 27mod waker;
25 28
29#[cfg(feature = "scheduler-deadline")]
30mod deadline;
31
26use core::future::Future; 32use core::future::Future;
27use core::marker::PhantomData; 33use core::marker::PhantomData;
28use core::mem; 34use core::mem;
@@ -31,8 +37,11 @@ use core::ptr::NonNull;
31#[cfg(not(feature = "arch-avr"))] 37#[cfg(not(feature = "arch-avr"))]
32use core::sync::atomic::AtomicPtr; 38use core::sync::atomic::AtomicPtr;
33use core::sync::atomic::Ordering; 39use core::sync::atomic::Ordering;
34use core::task::{Context, Poll}; 40use core::task::{Context, Poll, Waker};
35 41
42#[cfg(feature = "scheduler-deadline")]
43pub(crate) use deadline::Deadline;
44use embassy_executor_timer_queue::TimerQueueItem;
36#[cfg(feature = "arch-avr")] 45#[cfg(feature = "arch-avr")]
37use portable_atomic::AtomicPtr; 46use portable_atomic::AtomicPtr;
38 47
@@ -41,6 +50,12 @@ use self::state::State;
41use self::util::{SyncUnsafeCell, UninitCell}; 50use self::util::{SyncUnsafeCell, UninitCell};
42pub use self::waker::task_from_waker; 51pub use self::waker::task_from_waker;
43use super::SpawnToken; 52use super::SpawnToken;
53use crate::{Metadata, SpawnError};
54
55#[no_mangle]
56extern "Rust" fn __embassy_time_queue_item_from_waker(waker: &Waker) -> &'static mut TimerQueueItem {
57 unsafe { task_from_waker(waker).timer_queue_item() }
58}
44 59
45/// Raw task header for use in task pointers. 60/// Raw task header for use in task pointers.
46/// 61///
@@ -84,15 +99,21 @@ use super::SpawnToken;
84pub(crate) struct TaskHeader { 99pub(crate) struct TaskHeader {
85 pub(crate) state: State, 100 pub(crate) state: State,
86 pub(crate) run_queue_item: RunQueueItem, 101 pub(crate) run_queue_item: RunQueueItem,
102
87 pub(crate) executor: AtomicPtr<SyncExecutor>, 103 pub(crate) executor: AtomicPtr<SyncExecutor>,
88 poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>, 104 poll_fn: SyncUnsafeCell<Option<unsafe fn(TaskRef)>>,
89 105
90 /// Integrated timer queue storage. This field should not be accessed outside of the timer queue. 106 /// Integrated timer queue storage. This field should not be accessed outside of the timer queue.
91 pub(crate) timer_queue_item: timer_queue::TimerQueueItem, 107 pub(crate) timer_queue_item: TimerQueueItem,
108
109 pub(crate) metadata: Metadata,
110
111 #[cfg(feature = "rtos-trace")]
112 all_tasks_next: AtomicPtr<TaskHeader>,
92} 113}
93 114
94/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased. 115/// This is essentially a `&'static TaskStorage<F>` where the type of the future has been erased.
95#[derive(Clone, Copy, PartialEq)] 116#[derive(Debug, Clone, Copy, PartialEq)]
96pub struct TaskRef { 117pub struct TaskRef {
97 ptr: NonNull<TaskHeader>, 118 ptr: NonNull<TaskHeader>,
98} 119}
@@ -114,29 +135,27 @@ impl TaskRef {
114 } 135 }
115 } 136 }
116 137
117 /// # Safety
118 ///
119 /// The result of this function must only be compared
120 /// for equality, or stored, but not used.
121 pub const unsafe fn dangling() -> Self {
122 Self {
123 ptr: NonNull::dangling(),
124 }
125 }
126
127 pub(crate) fn header(self) -> &'static TaskHeader { 138 pub(crate) fn header(self) -> &'static TaskHeader {
128 unsafe { self.ptr.as_ref() } 139 unsafe { self.ptr.as_ref() }
129 } 140 }
130 141
142 pub(crate) fn metadata(self) -> &'static Metadata {
143 unsafe { &self.ptr.as_ref().metadata }
144 }
145
131 /// Returns a reference to the executor that the task is currently running on. 146 /// Returns a reference to the executor that the task is currently running on.
132 pub unsafe fn executor(self) -> Option<&'static Executor> { 147 pub unsafe fn executor(self) -> Option<&'static Executor> {
133 let executor = self.header().executor.load(Ordering::Relaxed); 148 let executor = self.header().executor.load(Ordering::Relaxed);
134 executor.as_ref().map(|e| Executor::wrap(e)) 149 executor.as_ref().map(|e| Executor::wrap(e))
135 } 150 }
136 151
137 /// Returns a reference to the timer queue item. 152 /// Returns a mutable reference to the timer queue item.
138 pub fn timer_queue_item(&self) -> &'static timer_queue::TimerQueueItem { 153 ///
139 &self.header().timer_queue_item 154 /// Safety
155 ///
156 /// This function must only be called in the context of the integrated timer queue.
157 pub unsafe fn timer_queue_item(mut self) -> &'static mut TimerQueueItem {
158 unsafe { &mut self.ptr.as_mut().timer_queue_item }
140 } 159 }
141 160
142 /// The returned pointer is valid for the entire TaskStorage. 161 /// The returned pointer is valid for the entire TaskStorage.
@@ -144,10 +163,10 @@ impl TaskRef {
144 self.ptr.as_ptr() 163 self.ptr.as_ptr()
145 } 164 }
146 165
147 /// Get the ID for a task 166 /// Returns the task ID.
148 #[cfg(feature = "trace")] 167 /// This can be used in combination with rtos-trace to match task names with IDs
149 pub fn as_id(self) -> u32 { 168 pub fn id(&self) -> u32 {
150 self.ptr.as_ptr() as u32 169 self.as_ptr() as u32
151 } 170 }
152} 171}
153 172
@@ -189,7 +208,10 @@ impl<F: Future + 'static> TaskStorage<F> {
189 // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` 208 // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss`
190 poll_fn: SyncUnsafeCell::new(None), 209 poll_fn: SyncUnsafeCell::new(None),
191 210
192 timer_queue_item: timer_queue::TimerQueueItem::new(), 211 timer_queue_item: TimerQueueItem::new(),
212 metadata: Metadata::new(),
213 #[cfg(feature = "rtos-trace")]
214 all_tasks_next: AtomicPtr::new(core::ptr::null_mut()),
193 }, 215 },
194 future: UninitCell::uninit(), 216 future: UninitCell::uninit(),
195 } 217 }
@@ -208,11 +230,11 @@ impl<F: Future + 'static> TaskStorage<F> {
208 /// 230 ///
209 /// Once the task has finished running, you may spawn it again. It is allowed to spawn it 231 /// Once the task has finished running, you may spawn it again. It is allowed to spawn it
210 /// on a different executor. 232 /// on a different executor.
211 pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { 233 pub fn spawn(&'static self, future: impl FnOnce() -> F) -> Result<SpawnToken<impl Sized>, SpawnError> {
212 let task = AvailableTask::claim(self); 234 let task = AvailableTask::claim(self);
213 match task { 235 match task {
214 Some(task) => task.initialize(future), 236 Some(task) => Ok(task.initialize(future)),
215 None => SpawnToken::new_failed(), 237 None => Err(SpawnError::Busy),
216 } 238 }
217 } 239 }
218 240
@@ -224,7 +246,7 @@ impl<F: Future + 'static> TaskStorage<F> {
224 let mut cx = Context::from_waker(&waker); 246 let mut cx = Context::from_waker(&waker);
225 match future.poll(&mut cx) { 247 match future.poll(&mut cx) {
226 Poll::Ready(_) => { 248 Poll::Ready(_) => {
227 #[cfg(feature = "trace")] 249 #[cfg(feature = "_any_trace")]
228 let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed); 250 let exec_ptr: *const SyncExecutor = this.raw.executor.load(Ordering::Relaxed);
229 251
230 // As the future has finished and this function will not be called 252 // As the future has finished and this function will not be called
@@ -239,7 +261,7 @@ impl<F: Future + 'static> TaskStorage<F> {
239 // after we're done with it. 261 // after we're done with it.
240 this.raw.state.despawn(); 262 this.raw.state.despawn();
241 263
242 #[cfg(feature = "trace")] 264 #[cfg(feature = "_any_trace")]
243 trace::task_end(exec_ptr, &p); 265 trace::task_end(exec_ptr, &p);
244 } 266 }
245 Poll::Pending => {} 267 Poll::Pending => {}
@@ -274,6 +296,7 @@ impl<F: Future + 'static> AvailableTask<F> {
274 296
275 fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> { 297 fn initialize_impl<S>(self, future: impl FnOnce() -> F) -> SpawnToken<S> {
276 unsafe { 298 unsafe {
299 self.task.raw.metadata.reset();
277 self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll)); 300 self.task.raw.poll_fn.set(Some(TaskStorage::<F>::poll));
278 self.task.future.write_in_place(future); 301 self.task.future.write_in_place(future);
279 302
@@ -340,10 +363,10 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
340 } 363 }
341 } 364 }
342 365
343 fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> SpawnToken<T> { 366 fn spawn_impl<T>(&'static self, future: impl FnOnce() -> F) -> Result<SpawnToken<T>, SpawnError> {
344 match self.pool.iter().find_map(AvailableTask::claim) { 367 match self.pool.iter().find_map(AvailableTask::claim) {
345 Some(task) => task.initialize_impl::<T>(future), 368 Some(task) => Ok(task.initialize_impl::<T>(future)),
346 None => SpawnToken::new_failed(), 369 None => Err(SpawnError::Busy),
347 } 370 }
348 } 371 }
349 372
@@ -354,7 +377,7 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
354 /// This will loop over the pool and spawn the task in the first storage that 377 /// This will loop over the pool and spawn the task in the first storage that
355 /// is currently free. If none is free, a "poisoned" SpawnToken is returned, 378 /// is currently free. If none is free, a "poisoned" SpawnToken is returned,
356 /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. 379 /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error.
357 pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken<impl Sized> { 380 pub fn spawn(&'static self, future: impl FnOnce() -> F) -> Result<SpawnToken<impl Sized>, SpawnError> {
358 self.spawn_impl::<F>(future) 381 self.spawn_impl::<F>(future)
359 } 382 }
360 383
@@ -367,7 +390,7 @@ impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
367 /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn` 390 /// SAFETY: `future` must be a closure of the form `move || my_async_fn(args)`, where `my_async_fn`
368 /// is an `async fn`, NOT a hand-written `Future`. 391 /// is an `async fn`, NOT a hand-written `Future`.
369 #[doc(hidden)] 392 #[doc(hidden)]
370 pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> SpawnToken<impl Sized> 393 pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> Result<SpawnToken<impl Sized>, SpawnError>
371 where 394 where
372 FutFn: FnOnce() -> F, 395 FutFn: FnOnce() -> F,
373 { 396 {
@@ -412,7 +435,7 @@ impl SyncExecutor {
412 /// - `task` must NOT be already enqueued (in this executor or another one). 435 /// - `task` must NOT be already enqueued (in this executor or another one).
413 #[inline(always)] 436 #[inline(always)]
414 unsafe fn enqueue(&self, task: TaskRef, l: state::Token) { 437 unsafe fn enqueue(&self, task: TaskRef, l: state::Token) {
415 #[cfg(feature = "trace")] 438 #[cfg(feature = "_any_trace")]
416 trace::task_ready_begin(self, &task); 439 trace::task_ready_begin(self, &task);
417 440
418 if self.run_queue.enqueue(task, l) { 441 if self.run_queue.enqueue(task, l) {
@@ -425,7 +448,7 @@ impl SyncExecutor {
425 .executor 448 .executor
426 .store((self as *const Self).cast_mut(), Ordering::Relaxed); 449 .store((self as *const Self).cast_mut(), Ordering::Relaxed);
427 450
428 #[cfg(feature = "trace")] 451 #[cfg(feature = "_any_trace")]
429 trace::task_new(self, &task); 452 trace::task_new(self, &task);
430 453
431 state::locked(|l| { 454 state::locked(|l| {
@@ -437,23 +460,23 @@ impl SyncExecutor {
437 /// 460 ///
438 /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. 461 /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created.
439 pub(crate) unsafe fn poll(&'static self) { 462 pub(crate) unsafe fn poll(&'static self) {
440 #[cfg(feature = "trace")] 463 #[cfg(feature = "_any_trace")]
441 trace::poll_start(self); 464 trace::poll_start(self);
442 465
443 self.run_queue.dequeue_all(|p| { 466 self.run_queue.dequeue_all(|p| {
444 let task = p.header(); 467 let task = p.header();
445 468
446 #[cfg(feature = "trace")] 469 #[cfg(feature = "_any_trace")]
447 trace::task_exec_begin(self, &p); 470 trace::task_exec_begin(self, &p);
448 471
449 // Run the task 472 // Run the task
450 task.poll_fn.get().unwrap_unchecked()(p); 473 task.poll_fn.get().unwrap_unchecked()(p);
451 474
452 #[cfg(feature = "trace")] 475 #[cfg(feature = "_any_trace")]
453 trace::task_exec_end(self, &p); 476 trace::task_exec_end(self, &p);
454 }); 477 });
455 478
456 #[cfg(feature = "trace")] 479 #[cfg(feature = "_any_trace")]
457 trace::executor_idle(self) 480 trace::executor_idle(self)
458 } 481 }
459} 482}
diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs
new file mode 100644
index 000000000..b8b052310
--- /dev/null
+++ b/embassy-executor/src/raw/run_queue.rs
@@ -0,0 +1,213 @@
1use core::ptr::{addr_of_mut, NonNull};
2
3use cordyceps::sorted_list::Links;
4use cordyceps::Linked;
5#[cfg(any(feature = "scheduler-priority", feature = "scheduler-deadline"))]
6use cordyceps::SortedList;
7
8#[cfg(target_has_atomic = "ptr")]
9type TransferStack<T> = cordyceps::TransferStack<T>;
10
11#[cfg(not(target_has_atomic = "ptr"))]
12type TransferStack<T> = MutexTransferStack<T>;
13
14use super::{TaskHeader, TaskRef};
15
16/// Use `cordyceps::sorted_list::Links` as the singly linked list
17/// for RunQueueItems.
18pub(crate) type RunQueueItem = Links<TaskHeader>;
19
20/// Implements the `Linked` trait, allowing for singly linked list usage
21/// of any of cordyceps' `TransferStack` (used for the atomic runqueue),
22/// `SortedList` (used with the DRS scheduler), or `Stack`, which is
23/// popped atomically from the `TransferStack`.
24unsafe impl Linked<Links<TaskHeader>> for TaskHeader {
25 type Handle = TaskRef;
26
27 // Convert a TaskRef into a TaskHeader ptr
28 fn into_ptr(r: TaskRef) -> NonNull<TaskHeader> {
29 r.ptr
30 }
31
32 // Convert a TaskHeader into a TaskRef
33 unsafe fn from_ptr(ptr: NonNull<TaskHeader>) -> TaskRef {
34 TaskRef { ptr }
35 }
36
37 // Given a pointer to a TaskHeader, obtain a pointer to the Links structure,
38 // which can be used to traverse to other TaskHeader nodes in the linked list
39 unsafe fn links(ptr: NonNull<TaskHeader>) -> NonNull<Links<TaskHeader>> {
40 let ptr: *mut TaskHeader = ptr.as_ptr();
41 NonNull::new_unchecked(addr_of_mut!((*ptr).run_queue_item))
42 }
43}
44
45/// Atomic task queue using a very, very simple lock-free linked-list queue:
46///
47/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
48///
49/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
50/// null. Then the batch is iterated following the next pointers until null is reached.
51///
52/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
53/// for our purposes: it can't create fairness problems since the next batch won't run until the
54/// current batch is completely processed, so even if a task enqueues itself instantly (for example
55/// by waking its own waker) can't prevent other tasks from running.
56pub(crate) struct RunQueue {
57 stack: TransferStack<TaskHeader>,
58}
59
60impl RunQueue {
61 pub const fn new() -> Self {
62 Self {
63 stack: TransferStack::new(),
64 }
65 }
66
67 /// Enqueues an item. Returns true if the queue was empty.
68 ///
69 /// # Safety
70 ///
71 /// `item` must NOT be already enqueued in any queue.
72 #[inline(always)]
73 pub(crate) unsafe fn enqueue(&self, task: TaskRef, _tok: super::state::Token) -> bool {
74 self.stack.push_was_empty(
75 task,
76 #[cfg(not(target_has_atomic = "ptr"))]
77 _tok,
78 )
79 }
80
81 /// # Standard atomic runqueue
82 ///
83 /// Empty the queue, then call `on_task` for each task that was in the queue.
84 /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
85 /// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
86 #[cfg(not(any(feature = "scheduler-priority", feature = "scheduler-deadline")))]
87 pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
88 let taken = self.stack.take_all();
89 for taskref in taken {
90 run_dequeue(&taskref);
91 on_task(taskref);
92 }
93 }
94
95 /// # Earliest Deadline First Scheduler
96 ///
97 /// This algorithm will loop until all enqueued tasks are processed.
98 ///
99 /// Before polling a task, all currently enqueued tasks will be popped from the
100 /// runqueue, and will be added to the working `sorted` list, a linked-list that
101 /// sorts tasks by their deadline, with nearest deadline items in the front, and
102 /// furthest deadline items in the back.
103 ///
104 /// After popping and sorting all pending tasks, the SOONEST task will be popped
105 /// from the front of the queue, and polled by calling `on_task` on it.
106 ///
107 /// This process will repeat until the local `sorted` queue AND the global
108 /// runqueue are both empty, at which point this function will return.
109 #[cfg(any(feature = "scheduler-priority", feature = "scheduler-deadline"))]
110 pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
111 let mut sorted = SortedList::<TaskHeader>::new_with_cmp(|lhs, rhs| {
112 // compare by priority first
113 #[cfg(feature = "scheduler-priority")]
114 {
115 let lp = lhs.metadata.priority();
116 let rp = rhs.metadata.priority();
117 if lp != rp {
118 return lp.cmp(&rp).reverse();
119 }
120 }
121 // compare deadlines in case of tie.
122 #[cfg(feature = "scheduler-deadline")]
123 {
124 let ld = lhs.metadata.deadline();
125 let rd = rhs.metadata.deadline();
126 if ld != rd {
127 return ld.cmp(&rd);
128 }
129 }
130 core::cmp::Ordering::Equal
131 });
132
133 loop {
134 // For each loop, grab any newly pended items
135 let taken = self.stack.take_all();
136
137 // Sort these into the list - this is potentially expensive! We do an
138 // insertion sort of new items, which iterates the linked list.
139 //
140 // Something on the order of `O(n * m)`, where `n` is the number
141 // of new tasks, and `m` is the number of already pending tasks.
142 sorted.extend(taken);
143
144 // Pop the task with the SOONEST deadline. If there are no tasks
145 // pending, then we are done.
146 let Some(taskref) = sorted.pop_front() else {
147 return;
148 };
149
150 // We got one task, mark it as dequeued, and process the task.
151 run_dequeue(&taskref);
152 on_task(taskref);
153 }
154 }
155}
156
157/// atomic state does not require a cs...
158#[cfg(target_has_atomic = "ptr")]
159#[inline(always)]
160fn run_dequeue(taskref: &TaskRef) {
161 taskref.header().state.run_dequeue();
162}
163
164/// ...while non-atomic state does
165#[cfg(not(target_has_atomic = "ptr"))]
166#[inline(always)]
167fn run_dequeue(taskref: &TaskRef) {
168 critical_section::with(|cs| {
169 taskref.header().state.run_dequeue(cs);
170 })
171}
172
173/// A wrapper type that acts like TransferStack by wrapping a normal Stack in a CS mutex
174#[cfg(not(target_has_atomic = "ptr"))]
175struct MutexTransferStack<T: Linked<cordyceps::stack::Links<T>>> {
176 inner: critical_section::Mutex<core::cell::UnsafeCell<cordyceps::Stack<T>>>,
177}
178
179#[cfg(not(target_has_atomic = "ptr"))]
180impl<T: Linked<cordyceps::stack::Links<T>>> MutexTransferStack<T> {
181 const fn new() -> Self {
182 Self {
183 inner: critical_section::Mutex::new(core::cell::UnsafeCell::new(cordyceps::Stack::new())),
184 }
185 }
186
187 /// Push an item to the transfer stack, returning whether the stack was previously empty
188 fn push_was_empty(&self, item: T::Handle, token: super::state::Token) -> bool {
189 // SAFETY: The critical-section mutex guarantees that there is no *concurrent* access
190 // for the lifetime of the token, but does NOT protect against re-entrant access.
191 // However, we never *return* the reference, nor do we recurse (or call another method
192 // like `take_all`) that could ever allow for re-entrant aliasing. Therefore, the
193 // presence of the critical section is sufficient to guarantee exclusive access to
194 // the `inner` field for the purposes of this function.
195 let inner = unsafe { &mut *self.inner.borrow(token).get() };
196 let is_empty = inner.is_empty();
197 inner.push(item);
198 is_empty
199 }
200
201 fn take_all(&self) -> cordyceps::Stack<T> {
202 critical_section::with(|cs| {
203 // SAFETY: The critical-section mutex guarantees that there is no *concurrent* access
204 // for the lifetime of the token, but does NOT protect against re-entrant access.
205 // However, we never *return* the reference, nor do we recurse (or call another method
206 // like `push_was_empty`) that could ever allow for re-entrant aliasing. Therefore, the
207 // presence of the critical section is sufficient to guarantee exclusive access to
208 // the `inner` field for the purposes of this function.
209 let inner = unsafe { &mut *self.inner.borrow(cs).get() };
210 inner.take_all()
211 })
212 }
213}
diff --git a/embassy-executor/src/raw/run_queue_atomics.rs b/embassy-executor/src/raw/run_queue_atomics.rs
deleted file mode 100644
index ce511d79a..000000000
--- a/embassy-executor/src/raw/run_queue_atomics.rs
+++ /dev/null
@@ -1,88 +0,0 @@
1use core::ptr;
2use core::ptr::NonNull;
3use core::sync::atomic::{AtomicPtr, Ordering};
4
5use super::{TaskHeader, TaskRef};
6use crate::raw::util::SyncUnsafeCell;
7
8pub(crate) struct RunQueueItem {
9 next: SyncUnsafeCell<Option<TaskRef>>,
10}
11
12impl RunQueueItem {
13 pub const fn new() -> Self {
14 Self {
15 next: SyncUnsafeCell::new(None),
16 }
17 }
18}
19
20/// Atomic task queue using a very, very simple lock-free linked-list queue:
21///
22/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
23///
24/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
25/// null. Then the batch is iterated following the next pointers until null is reached.
26///
27/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
28/// for our purposes: it can't create fairness problems since the next batch won't run until the
29/// current batch is completely processed, so even if a task enqueues itself instantly (for example
30/// by waking its own waker) can't prevent other tasks from running.
31pub(crate) struct RunQueue {
32 head: AtomicPtr<TaskHeader>,
33}
34
35impl RunQueue {
36 pub const fn new() -> Self {
37 Self {
38 head: AtomicPtr::new(ptr::null_mut()),
39 }
40 }
41
42 /// Enqueues an item. Returns true if the queue was empty.
43 ///
44 /// # Safety
45 ///
46 /// `item` must NOT be already enqueued in any queue.
47 #[inline(always)]
48 pub(crate) unsafe fn enqueue(&self, task: TaskRef, _: super::state::Token) -> bool {
49 let mut was_empty = false;
50
51 self.head
52 .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| {
53 was_empty = prev.is_null();
54 unsafe {
55 // safety: the pointer is either null or valid
56 let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr()));
57 // safety: there are no concurrent accesses to `next`
58 task.header().run_queue_item.next.set(prev);
59 }
60 Some(task.as_ptr() as *mut _)
61 })
62 .ok();
63
64 was_empty
65 }
66
67 /// Empty the queue, then call `on_task` for each task that was in the queue.
68 /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
69 /// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
70 pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
71 // Atomically empty the queue.
72 let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel);
73
74 // safety: the pointer is either null or valid
75 let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) };
76
77 // Iterate the linked list of tasks that were previously in the queue.
78 while let Some(task) = next {
79 // If the task re-enqueues itself, the `next` pointer will get overwritten.
80 // Therefore, first read the next pointer, and only then process the task.
81 // safety: there are no concurrent accesses to `next`
82 next = unsafe { task.header().run_queue_item.next.get() };
83
84 task.header().state.run_dequeue();
85 on_task(task);
86 }
87 }
88}
diff --git a/embassy-executor/src/raw/run_queue_critical_section.rs b/embassy-executor/src/raw/run_queue_critical_section.rs
deleted file mode 100644
index 86c4085ed..000000000
--- a/embassy-executor/src/raw/run_queue_critical_section.rs
+++ /dev/null
@@ -1,74 +0,0 @@
1use core::cell::Cell;
2
3use critical_section::{CriticalSection, Mutex};
4
5use super::TaskRef;
6
7pub(crate) struct RunQueueItem {
8 next: Mutex<Cell<Option<TaskRef>>>,
9}
10
11impl RunQueueItem {
12 pub const fn new() -> Self {
13 Self {
14 next: Mutex::new(Cell::new(None)),
15 }
16 }
17}
18
19/// Atomic task queue using a very, very simple lock-free linked-list queue:
20///
21/// To enqueue a task, task.next is set to the old head, and head is atomically set to task.
22///
23/// Dequeuing is done in batches: the queue is emptied by atomically replacing head with
24/// null. Then the batch is iterated following the next pointers until null is reached.
25///
26/// Note that batches will be iterated in the reverse order as they were enqueued. This is OK
27/// for our purposes: it can't create fairness problems since the next batch won't run until the
28/// current batch is completely processed, so even if a task enqueues itself instantly (for example
29/// by waking its own waker) can't prevent other tasks from running.
30pub(crate) struct RunQueue {
31 head: Mutex<Cell<Option<TaskRef>>>,
32}
33
34impl RunQueue {
35 pub const fn new() -> Self {
36 Self {
37 head: Mutex::new(Cell::new(None)),
38 }
39 }
40
41 /// Enqueues an item. Returns true if the queue was empty.
42 ///
43 /// # Safety
44 ///
45 /// `item` must NOT be already enqueued in any queue.
46 #[inline(always)]
47 pub(crate) unsafe fn enqueue(&self, task: TaskRef, cs: CriticalSection<'_>) -> bool {
48 let prev = self.head.borrow(cs).replace(Some(task));
49 task.header().run_queue_item.next.borrow(cs).set(prev);
50
51 prev.is_none()
52 }
53
54 /// Empty the queue, then call `on_task` for each task that was in the queue.
55 /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue
56 /// and will be processed by the *next* call to `dequeue_all`, *not* the current one.
57 pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) {
58 // Atomically empty the queue.
59 let mut next = critical_section::with(|cs| self.head.borrow(cs).take());
60
61 // Iterate the linked list of tasks that were previously in the queue.
62 while let Some(task) = next {
63 // If the task re-enqueues itself, the `next` pointer will get overwritten.
64 // Therefore, first read the next pointer, and only then process the task.
65
66 critical_section::with(|cs| {
67 next = task.header().run_queue_item.next.borrow(cs).get();
68 task.header().state.run_dequeue(cs);
69 });
70
71 on_task(task);
72 }
73 }
74}
diff --git a/embassy-executor/src/raw/state_atomics.rs b/embassy-executor/src/raw/state_atomics.rs
index b6576bfc2..6675875be 100644
--- a/embassy-executor/src/raw/state_atomics.rs
+++ b/embassy-executor/src/raw/state_atomics.rs
@@ -1,4 +1,15 @@
1use core::sync::atomic::{AtomicU32, Ordering}; 1// Prefer pointer-width atomic operations, as narrower ones may be slower.
2#[cfg(all(target_pointer_width = "32", target_has_atomic = "32"))]
3type AtomicState = core::sync::atomic::AtomicU32;
4#[cfg(not(all(target_pointer_width = "32", target_has_atomic = "32")))]
5type AtomicState = core::sync::atomic::AtomicU8;
6
7#[cfg(all(target_pointer_width = "32", target_has_atomic = "32"))]
8type StateBits = u32;
9#[cfg(not(all(target_pointer_width = "32", target_has_atomic = "32")))]
10type StateBits = u8;
11
12use core::sync::atomic::Ordering;
2 13
3#[derive(Clone, Copy)] 14#[derive(Clone, Copy)]
4pub(crate) struct Token(()); 15pub(crate) struct Token(());
@@ -11,18 +22,18 @@ pub(crate) fn locked<R>(f: impl FnOnce(Token) -> R) -> R {
11} 22}
12 23
13/// Task is spawned (has a future) 24/// Task is spawned (has a future)
14pub(crate) const STATE_SPAWNED: u32 = 1 << 0; 25pub(crate) const STATE_SPAWNED: StateBits = 1 << 0;
15/// Task is in the executor run queue 26/// Task is in the executor run queue
16pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; 27pub(crate) const STATE_RUN_QUEUED: StateBits = 1 << 1;
17 28
18pub(crate) struct State { 29pub(crate) struct State {
19 state: AtomicU32, 30 state: AtomicState,
20} 31}
21 32
22impl State { 33impl State {
23 pub const fn new() -> State { 34 pub const fn new() -> State {
24 Self { 35 Self {
25 state: AtomicU32::new(0), 36 state: AtomicState::new(0),
26 } 37 }
27 } 38 }
28 39
diff --git a/embassy-executor/src/raw/state_critical_section.rs b/embassy-executor/src/raw/state_critical_section.rs
index 6b627ff79..b69a6ac66 100644
--- a/embassy-executor/src/raw/state_critical_section.rs
+++ b/embassy-executor/src/raw/state_critical_section.rs
@@ -3,13 +3,18 @@ use core::cell::Cell;
3pub(crate) use critical_section::{with as locked, CriticalSection as Token}; 3pub(crate) use critical_section::{with as locked, CriticalSection as Token};
4use critical_section::{CriticalSection, Mutex}; 4use critical_section::{CriticalSection, Mutex};
5 5
6#[cfg(target_arch = "avr")]
7type StateBits = u8;
8#[cfg(not(target_arch = "avr"))]
9type StateBits = usize;
10
6/// Task is spawned (has a future) 11/// Task is spawned (has a future)
7pub(crate) const STATE_SPAWNED: u32 = 1 << 0; 12pub(crate) const STATE_SPAWNED: StateBits = 1 << 0;
8/// Task is in the executor run queue 13/// Task is in the executor run queue
9pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; 14pub(crate) const STATE_RUN_QUEUED: StateBits = 1 << 1;
10 15
11pub(crate) struct State { 16pub(crate) struct State {
12 state: Mutex<Cell<u32>>, 17 state: Mutex<Cell<StateBits>>,
13} 18}
14 19
15impl State { 20impl State {
@@ -19,11 +24,11 @@ impl State {
19 } 24 }
20 } 25 }
21 26
22 fn update<R>(&self, f: impl FnOnce(&mut u32) -> R) -> R { 27 fn update<R>(&self, f: impl FnOnce(&mut StateBits) -> R) -> R {
23 critical_section::with(|cs| self.update_with_cs(cs, f)) 28 critical_section::with(|cs| self.update_with_cs(cs, f))
24 } 29 }
25 30
26 fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut u32) -> R) -> R { 31 fn update_with_cs<R>(&self, cs: CriticalSection<'_>, f: impl FnOnce(&mut StateBits) -> R) -> R {
27 let s = self.state.borrow(cs); 32 let s = self.state.borrow(cs);
28 let mut val = s.get(); 33 let mut val = s.get();
29 let r = f(&mut val); 34 let r = f(&mut val);
diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs
deleted file mode 100644
index e52453be4..000000000
--- a/embassy-executor/src/raw/timer_queue.rs
+++ /dev/null
@@ -1,73 +0,0 @@
1//! Timer queue operations.
2
3use core::cell::Cell;
4
5use super::TaskRef;
6
7#[cfg(feature = "_timer-item-payload")]
8macro_rules! define_opaque {
9 ($size:tt) => {
10 /// An opaque data type.
11 #[repr(align($size))]
12 pub struct OpaqueData {
13 data: [u8; $size],
14 }
15
16 impl OpaqueData {
17 const fn new() -> Self {
18 Self { data: [0; $size] }
19 }
20
21 /// Access the data as a reference to a type `T`.
22 ///
23 /// Safety:
24 ///
25 /// The caller must ensure that the size of the type `T` is less than, or equal to
26 /// the size of the payload, and must ensure that the alignment of the type `T` is
27 /// less than, or equal to the alignment of the payload.
28 ///
29 /// The type must be valid when zero-initialized.
30 pub unsafe fn as_ref<T>(&self) -> &T {
31 &*(self.data.as_ptr() as *const T)
32 }
33 }
34 };
35}
36
37#[cfg(feature = "timer-item-payload-size-1")]
38define_opaque!(1);
39#[cfg(feature = "timer-item-payload-size-2")]
40define_opaque!(2);
41#[cfg(feature = "timer-item-payload-size-4")]
42define_opaque!(4);
43#[cfg(feature = "timer-item-payload-size-8")]
44define_opaque!(8);
45
46/// An item in the timer queue.
47pub struct TimerQueueItem {
48 /// The next item in the queue.
49 ///
50 /// If this field contains `Some`, the item is in the queue. The last item in the queue has a
51 /// value of `Some(dangling_pointer)`
52 pub next: Cell<Option<TaskRef>>,
53
54 /// The time at which this item expires.
55 pub expires_at: Cell<u64>,
56
57 /// Some implementation-defined, zero-initialized piece of data.
58 #[cfg(feature = "_timer-item-payload")]
59 pub payload: OpaqueData,
60}
61
62unsafe impl Sync for TimerQueueItem {}
63
64impl TimerQueueItem {
65 pub(crate) const fn new() -> Self {
66 Self {
67 next: Cell::new(None),
68 expires_at: Cell::new(0),
69 #[cfg(feature = "_timer-item-payload")]
70 payload: OpaqueData::new(),
71 }
72 }
73}
diff --git a/embassy-executor/src/raw/trace.rs b/embassy-executor/src/raw/trace.rs
index aba519c8f..b3086948c 100644
--- a/embassy-executor/src/raw/trace.rs
+++ b/embassy-executor/src/raw/trace.rs
@@ -81,9 +81,94 @@
81 81
82#![allow(unused)] 82#![allow(unused)]
83 83
84use crate::raw::{SyncExecutor, TaskRef}; 84use core::cell::UnsafeCell;
85use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
85 86
86#[cfg(not(feature = "rtos-trace"))] 87#[cfg(feature = "rtos-trace")]
88use rtos_trace::TaskInfo;
89
90use crate::raw::{SyncExecutor, TaskHeader, TaskRef};
91use crate::spawner::{SpawnError, SpawnToken, Spawner};
92
93/// Global task tracker instance
94///
95/// This static provides access to the global task tracker which maintains
96/// a list of all tasks in the system. It's automatically updated by the
97/// task lifecycle hooks in the trace module.
98#[cfg(feature = "rtos-trace")]
99pub(crate) static TASK_TRACKER: TaskTracker = TaskTracker::new();
100
101/// A thread-safe tracker for all tasks in the system
102///
103/// This struct uses an intrusive linked list approach to track all tasks
104/// without additional memory allocations. It maintains a global list of
105/// tasks that can be traversed to find all currently existing tasks.
106#[cfg(feature = "rtos-trace")]
107pub(crate) struct TaskTracker {
108 head: AtomicPtr<TaskHeader>,
109}
110
111#[cfg(feature = "rtos-trace")]
112impl TaskTracker {
113 /// Creates a new empty task tracker
114 ///
115 /// Initializes a tracker with no tasks in its list.
116 pub const fn new() -> Self {
117 Self {
118 head: AtomicPtr::new(core::ptr::null_mut()),
119 }
120 }
121
122 /// Adds a task to the tracker
123 ///
124 /// This method inserts a task at the head of the intrusive linked list.
125 /// The operation is thread-safe and lock-free, using atomic operations
126 /// to ensure consistency even when called from different contexts.
127 ///
128 /// # Arguments
129 /// * `task` - The task reference to add to the tracker
130 pub fn add(&self, task: TaskRef) {
131 let task_ptr = task.as_ptr();
132
133 loop {
134 let current_head = self.head.load(Ordering::Acquire);
135 unsafe {
136 (*task_ptr).all_tasks_next.store(current_head, Ordering::Relaxed);
137 }
138
139 if self
140 .head
141 .compare_exchange(current_head, task_ptr.cast_mut(), Ordering::Release, Ordering::Relaxed)
142 .is_ok()
143 {
144 break;
145 }
146 }
147 }
148
149 /// Performs an operation on each task in the tracker
150 ///
151 /// This method traverses the entire list of tasks and calls the provided
152 /// function for each task. This allows inspecting or processing all tasks
153 /// in the system without modifying the tracker's structure.
154 ///
155 /// # Arguments
156 /// * `f` - A function to call for each task in the tracker
157 pub fn for_each<F>(&self, mut f: F)
158 where
159 F: FnMut(TaskRef),
160 {
161 let mut current = self.head.load(Ordering::Acquire);
162 while !current.is_null() {
163 let task = unsafe { TaskRef::from_ptr(current) };
164 f(task);
165
166 current = unsafe { (*current).all_tasks_next.load(Ordering::Acquire) };
167 }
168 }
169}
170
171#[cfg(feature = "trace")]
87extern "Rust" { 172extern "Rust" {
88 /// This callback is called when the executor begins polling. This will always 173 /// This callback is called when the executor begins polling. This will always
89 /// be paired with a later call to `_embassy_trace_executor_idle`. 174 /// be paired with a later call to `_embassy_trace_executor_idle`.
@@ -145,7 +230,7 @@ extern "Rust" {
145 230
146#[inline] 231#[inline]
147pub(crate) fn poll_start(executor: &SyncExecutor) { 232pub(crate) fn poll_start(executor: &SyncExecutor) {
148 #[cfg(not(feature = "rtos-trace"))] 233 #[cfg(feature = "trace")]
149 unsafe { 234 unsafe {
150 _embassy_trace_poll_start(executor as *const _ as u32) 235 _embassy_trace_poll_start(executor as *const _ as u32)
151 } 236 }
@@ -153,18 +238,31 @@ pub(crate) fn poll_start(executor: &SyncExecutor) {
153 238
154#[inline] 239#[inline]
155pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) { 240pub(crate) fn task_new(executor: &SyncExecutor, task: &TaskRef) {
156 #[cfg(not(feature = "rtos-trace"))] 241 #[cfg(feature = "trace")]
157 unsafe { 242 unsafe {
158 _embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32) 243 _embassy_trace_task_new(executor as *const _ as u32, task.as_ptr() as u32)
159 } 244 }
160 245
161 #[cfg(feature = "rtos-trace")] 246 #[cfg(feature = "rtos-trace")]
162 rtos_trace::trace::task_new(task.as_ptr() as u32); 247 {
248 rtos_trace::trace::task_new(task.as_ptr() as u32);
249 let name = task.metadata().name().unwrap_or("unnamed task\0");
250 let info = rtos_trace::TaskInfo {
251 name,
252 priority: 0,
253 stack_base: 0,
254 stack_size: 0,
255 };
256 rtos_trace::trace::task_send_info(task.id(), info);
257 }
258
259 #[cfg(feature = "rtos-trace")]
260 TASK_TRACKER.add(*task);
163} 261}
164 262
165#[inline] 263#[inline]
166pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) { 264pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) {
167 #[cfg(not(feature = "rtos-trace"))] 265 #[cfg(feature = "trace")]
168 unsafe { 266 unsafe {
169 _embassy_trace_task_end(executor as u32, task.as_ptr() as u32) 267 _embassy_trace_task_end(executor as u32, task.as_ptr() as u32)
170 } 268 }
@@ -172,7 +270,7 @@ pub(crate) fn task_end(executor: *const SyncExecutor, task: &TaskRef) {
172 270
173#[inline] 271#[inline]
174pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) { 272pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) {
175 #[cfg(not(feature = "rtos-trace"))] 273 #[cfg(feature = "trace")]
176 unsafe { 274 unsafe {
177 _embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32) 275 _embassy_trace_task_ready_begin(executor as *const _ as u32, task.as_ptr() as u32)
178 } 276 }
@@ -182,7 +280,7 @@ pub(crate) fn task_ready_begin(executor: &SyncExecutor, task: &TaskRef) {
182 280
183#[inline] 281#[inline]
184pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) { 282pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) {
185 #[cfg(not(feature = "rtos-trace"))] 283 #[cfg(feature = "trace")]
186 unsafe { 284 unsafe {
187 _embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32) 285 _embassy_trace_task_exec_begin(executor as *const _ as u32, task.as_ptr() as u32)
188 } 286 }
@@ -192,7 +290,7 @@ pub(crate) fn task_exec_begin(executor: &SyncExecutor, task: &TaskRef) {
192 290
193#[inline] 291#[inline]
194pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) { 292pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) {
195 #[cfg(not(feature = "rtos-trace"))] 293 #[cfg(feature = "trace")]
196 unsafe { 294 unsafe {
197 _embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32) 295 _embassy_trace_task_exec_end(executor as *const _ as u32, task.as_ptr() as u32)
198 } 296 }
@@ -202,7 +300,7 @@ pub(crate) fn task_exec_end(executor: &SyncExecutor, task: &TaskRef) {
202 300
203#[inline] 301#[inline]
204pub(crate) fn executor_idle(executor: &SyncExecutor) { 302pub(crate) fn executor_idle(executor: &SyncExecutor) {
205 #[cfg(not(feature = "rtos-trace"))] 303 #[cfg(feature = "trace")]
206 unsafe { 304 unsafe {
207 _embassy_trace_executor_idle(executor as *const _ as u32) 305 _embassy_trace_executor_idle(executor as *const _ as u32)
208 } 306 }
@@ -210,10 +308,63 @@ pub(crate) fn executor_idle(executor: &SyncExecutor) {
210 rtos_trace::trace::system_idle(); 308 rtos_trace::trace::system_idle();
211} 309}
212 310
311/// Returns an iterator over all active tasks in the system
312///
313/// This function provides a convenient way to iterate over all tasks
314/// that are currently tracked in the system. The returned iterator
315/// yields each task in the global task tracker.
316///
317/// # Returns
318/// An iterator that yields `TaskRef` items for each task
319#[cfg(feature = "rtos-trace")]
320fn get_all_active_tasks() -> impl Iterator<Item = TaskRef> + 'static {
321 struct TaskIterator<'a> {
322 tracker: &'a TaskTracker,
323 current: *mut TaskHeader,
324 }
325
326 impl<'a> Iterator for TaskIterator<'a> {
327 type Item = TaskRef;
328
329 fn next(&mut self) -> Option<Self::Item> {
330 if self.current.is_null() {
331 return None;
332 }
333
334 let task = unsafe { TaskRef::from_ptr(self.current) };
335 self.current = unsafe { (*self.current).all_tasks_next.load(Ordering::Acquire) };
336
337 Some(task)
338 }
339 }
340
341 TaskIterator {
342 tracker: &TASK_TRACKER,
343 current: TASK_TRACKER.head.load(Ordering::Acquire),
344 }
345}
346
347/// Perform an action on each active task
348#[cfg(feature = "rtos-trace")]
349fn with_all_active_tasks<F>(f: F)
350where
351 F: FnMut(TaskRef),
352{
353 TASK_TRACKER.for_each(f);
354}
355
213#[cfg(feature = "rtos-trace")] 356#[cfg(feature = "rtos-trace")]
214impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor { 357impl rtos_trace::RtosTraceOSCallbacks for crate::raw::SyncExecutor {
215 fn task_list() { 358 fn task_list() {
216 // We don't know what tasks exist, so we can't send them. 359 with_all_active_tasks(|task| {
360 let info = rtos_trace::TaskInfo {
361 name: task.metadata().name().unwrap_or("unnamed task\0"),
362 priority: 0,
363 stack_base: 0,
364 stack_size: 0,
365 };
366 rtos_trace::trace::task_send_info(task.id(), info);
367 });
217 } 368 }
218 fn time() -> u64 { 369 fn time() -> u64 {
219 const fn gcd(a: u64, b: u64) -> u64 { 370 const fn gcd(a: u64, b: u64) -> u64 {
diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs
index ff243081c..83d896b76 100644
--- a/embassy-executor/src/spawner.rs
+++ b/embassy-executor/src/spawner.rs
@@ -5,6 +5,7 @@ use core::sync::atomic::Ordering;
5use core::task::Poll; 5use core::task::Poll;
6 6
7use super::raw; 7use super::raw;
8use crate::Metadata;
8 9
9/// Token to spawn a newly-created task in an executor. 10/// Token to spawn a newly-created task in an executor.
10/// 11///
@@ -22,33 +23,28 @@ use super::raw;
22/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. 23/// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it.
23#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] 24#[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"]
24pub struct SpawnToken<S> { 25pub struct SpawnToken<S> {
25 raw_task: Option<raw::TaskRef>, 26 pub(crate) raw_task: raw::TaskRef,
26 phantom: PhantomData<*mut S>, 27 phantom: PhantomData<*mut S>,
27} 28}
28 29
29impl<S> SpawnToken<S> { 30impl<S> SpawnToken<S> {
30 pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { 31 pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self {
31 Self { 32 Self {
32 raw_task: Some(raw_task), 33 raw_task,
33 phantom: PhantomData, 34 phantom: PhantomData,
34 } 35 }
35 } 36 }
36 37
37 /// Returns the task id if available, otherwise 0 38 /// Returns the task ID.
38 /// This can be used in combination with rtos-trace to match task names with id's 39 /// This can be used in combination with rtos-trace to match task names with IDs
39 pub fn id(&self) -> u32 { 40 pub fn id(&self) -> u32 {
40 match self.raw_task { 41 self.raw_task.id()
41 None => 0,
42 Some(t) => t.as_ptr() as u32,
43 }
44 } 42 }
45 43
46 /// Return a SpawnToken that represents a failed spawn. 44 /// Get the metadata for this task. You can use this to set metadata fields
47 pub fn new_failed() -> Self { 45 /// prior to spawning it.
48 Self { 46 pub fn metadata(&self) -> &Metadata {
49 raw_task: None, 47 self.raw_task.metadata()
50 phantom: PhantomData,
51 }
52 } 48 }
53} 49}
54 50
@@ -103,7 +99,7 @@ impl core::error::Error for SpawnError {}
103/// If you want to spawn tasks from another thread, use [SendSpawner]. 99/// If you want to spawn tasks from another thread, use [SendSpawner].
104#[derive(Copy, Clone)] 100#[derive(Copy, Clone)]
105pub struct Spawner { 101pub struct Spawner {
106 executor: &'static raw::Executor, 102 pub(crate) executor: &'static raw::Executor,
107 not_send: PhantomData<*mut ()>, 103 not_send: PhantomData<*mut ()>,
108} 104}
109 105
@@ -120,10 +116,26 @@ impl Spawner {
120 /// This function is `async` just to get access to the current async 116 /// This function is `async` just to get access to the current async
121 /// context. It returns instantly, it does not block/yield. 117 /// context. It returns instantly, it does not block/yield.
122 /// 118 ///
119 /// Using this method is discouraged due to it being unsafe. Consider the following
120 /// alternatives instead:
121 ///
122 /// - Pass the initial `Spawner` as an argument to tasks. Note that it's `Copy`, so you can
123 /// make as many copies of it as you want.
124 /// - Use `SendSpawner::for_current_executor()` instead, which is safe but can only be used
125 /// if task arguments are `Send`.
126 ///
127 /// The only case where using this method is absolutely required is obtaining the `Spawner`
128 /// for an `InterruptExecutor`.
129 ///
130 /// # Safety
131 ///
132 /// You must only execute this with an async `Context` created by the Embassy executor.
133 /// You must not execute it with manually-created `Context`s.
134 ///
123 /// # Panics 135 /// # Panics
124 /// 136 ///
125 /// Panics if the current executor is not an Embassy executor. 137 /// Panics if the current executor is not an Embassy executor.
126 pub fn for_current_executor() -> impl Future<Output = Self> { 138 pub unsafe fn for_current_executor() -> impl Future<Output = Self> {
127 poll_fn(|cx| { 139 poll_fn(|cx| {
128 let task = raw::task_from_waker(cx.waker()); 140 let task = raw::task_from_waker(cx.waker());
129 let executor = unsafe { 141 let executor = unsafe {
@@ -141,30 +153,10 @@ impl Spawner {
141 /// Spawn a task into an executor. 153 /// Spawn a task into an executor.
142 /// 154 ///
143 /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). 155 /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`).
144 pub fn spawn<S>(&self, token: SpawnToken<S>) -> Result<(), SpawnError> { 156 pub fn spawn<S>(&self, token: SpawnToken<S>) {
145 let task = token.raw_task; 157 let task = token.raw_task;
146 mem::forget(token); 158 mem::forget(token);
147 159 unsafe { self.executor.spawn(task) }
148 match task {
149 Some(task) => {
150 unsafe { self.executor.spawn(task) };
151 Ok(())
152 }
153 None => Err(SpawnError::Busy),
154 }
155 }
156
157 // Used by the `embassy_executor_macros::main!` macro to throw an error when spawn
158 // fails. This is here to allow conditional use of `defmt::unwrap!`
159 // without introducing a `defmt` feature in the `embassy_executor_macros` package,
160 // which would require use of `-Z namespaced-features`.
161 /// Spawn a task into an executor, panicking on failure.
162 ///
163 /// # Panics
164 ///
165 /// Panics if the spawning fails.
166 pub fn must_spawn<S>(&self, token: SpawnToken<S>) {
167 unwrap!(self.spawn(token));
168 } 160 }
169 161
170 /// Convert this Spawner to a SendSpawner. This allows you to send the 162 /// Convert this Spawner to a SendSpawner. This allows you to send the
@@ -222,25 +214,9 @@ impl SendSpawner {
222 /// Spawn a task into an executor. 214 /// Spawn a task into an executor.
223 /// 215 ///
224 /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`). 216 /// You obtain the `token` by calling a task function (i.e. one marked with `#[embassy_executor::task]`).
225 pub fn spawn<S: Send>(&self, token: SpawnToken<S>) -> Result<(), SpawnError> { 217 pub fn spawn<S: Send>(&self, token: SpawnToken<S>) {
226 let header = token.raw_task; 218 let header = token.raw_task;
227 mem::forget(token); 219 mem::forget(token);
228 220 unsafe { self.executor.spawn(header) }
229 match header {
230 Some(header) => {
231 unsafe { self.executor.spawn(header) };
232 Ok(())
233 }
234 None => Err(SpawnError::Busy),
235 }
236 }
237
238 /// Spawn a task into an executor, panicking on failure.
239 ///
240 /// # Panics
241 ///
242 /// Panics if the spawning fails.
243 pub fn must_spawn<S: Send>(&self, token: SpawnToken<S>) {
244 unwrap!(self.spawn(token));
245 } 221 }
246} 222}
diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs
index 78c49c071..6baf3dc21 100644
--- a/embassy-executor/tests/test.rs
+++ b/embassy-executor/tests/test.rs
@@ -1,12 +1,13 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))] 1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2#![cfg_attr(feature = "nightly", feature(never_type))]
2 3
3use std::boxed::Box; 4use std::boxed::Box;
4use std::future::poll_fn; 5use std::future::{poll_fn, Future};
5use std::sync::{Arc, Mutex}; 6use std::sync::{Arc, Mutex};
6use std::task::Poll; 7use std::task::Poll;
7 8
8use embassy_executor::raw::Executor; 9use embassy_executor::raw::Executor;
9use embassy_executor::task; 10use embassy_executor::{task, Spawner};
10 11
11#[export_name = "__pender"] 12#[export_name = "__pender"]
12fn __pender(context: *mut ()) { 13fn __pender(context: *mut ()) {
@@ -58,8 +59,41 @@ fn executor_task() {
58 trace.push("poll task1") 59 trace.push("poll task1")
59 } 60 }
60 61
62 #[task]
63 async fn task2() -> ! {
64 panic!()
65 }
66
61 let (executor, trace) = setup(); 67 let (executor, trace) = setup();
62 executor.spawner().spawn(task1(trace.clone())).unwrap(); 68 executor.spawner().spawn(task1(trace.clone()).unwrap());
69
70 unsafe { executor.poll() };
71 unsafe { executor.poll() };
72
73 assert_eq!(
74 trace.get(),
75 &[
76 "pend", // spawning a task pends the executor
77 "poll task1", // poll only once.
78 ]
79 )
80}
81
82#[test]
83fn executor_task_rpit() {
84 #[task]
85 fn task1(trace: Trace) -> impl Future<Output = ()> {
86 async move { trace.push("poll task1") }
87 }
88
89 #[cfg(feature = "nightly")]
90 #[task]
91 fn task2() -> impl Future<Output = !> {
92 async { panic!() }
93 }
94
95 let (executor, trace) = setup();
96 executor.spawner().spawn(task1(trace.clone()).unwrap());
63 97
64 unsafe { executor.poll() }; 98 unsafe { executor.poll() };
65 unsafe { executor.poll() }; 99 unsafe { executor.poll() };
@@ -86,7 +120,7 @@ fn executor_task_self_wake() {
86 } 120 }
87 121
88 let (executor, trace) = setup(); 122 let (executor, trace) = setup();
89 executor.spawner().spawn(task1(trace.clone())).unwrap(); 123 executor.spawner().spawn(task1(trace.clone()).unwrap());
90 124
91 unsafe { executor.poll() }; 125 unsafe { executor.poll() };
92 unsafe { executor.poll() }; 126 unsafe { executor.poll() };
@@ -118,7 +152,7 @@ fn executor_task_self_wake_twice() {
118 } 152 }
119 153
120 let (executor, trace) = setup(); 154 let (executor, trace) = setup();
121 executor.spawner().spawn(task1(trace.clone())).unwrap(); 155 executor.spawner().spawn(task1(trace.clone()).unwrap());
122 156
123 unsafe { executor.poll() }; 157 unsafe { executor.poll() };
124 unsafe { executor.poll() }; 158 unsafe { executor.poll() };
@@ -154,7 +188,7 @@ fn waking_after_completion_does_not_poll() {
154 let waker = Box::leak(Box::new(AtomicWaker::new())); 188 let waker = Box::leak(Box::new(AtomicWaker::new()));
155 189
156 let (executor, trace) = setup(); 190 let (executor, trace) = setup();
157 executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); 191 executor.spawner().spawn(task1(trace.clone(), waker).unwrap());
158 192
159 unsafe { executor.poll() }; 193 unsafe { executor.poll() };
160 waker.wake(); 194 waker.wake();
@@ -166,7 +200,7 @@ fn waking_after_completion_does_not_poll() {
166 unsafe { executor.poll() }; // Clears running status 200 unsafe { executor.poll() }; // Clears running status
167 201
168 // Can respawn waken-but-dead task 202 // Can respawn waken-but-dead task
169 executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); 203 executor.spawner().spawn(task1(trace.clone(), waker).unwrap());
170 204
171 unsafe { executor.poll() }; 205 unsafe { executor.poll() };
172 206
@@ -216,7 +250,7 @@ fn waking_with_old_waker_after_respawn() {
216 let waker = Box::leak(Box::new(AtomicWaker::new())); 250 let waker = Box::leak(Box::new(AtomicWaker::new()));
217 251
218 let (executor, trace) = setup(); 252 let (executor, trace) = setup();
219 executor.spawner().spawn(task1(trace.clone(), waker)).unwrap(); 253 executor.spawner().spawn(task1(trace.clone(), waker).unwrap());
220 254
221 unsafe { executor.poll() }; 255 unsafe { executor.poll() };
222 unsafe { executor.poll() }; // progress to registering the waker 256 unsafe { executor.poll() }; // progress to registering the waker
@@ -239,8 +273,7 @@ fn waking_with_old_waker_after_respawn() {
239 let (other_executor, other_trace) = setup(); 273 let (other_executor, other_trace) = setup();
240 other_executor 274 other_executor
241 .spawner() 275 .spawner()
242 .spawn(task1(other_trace.clone(), waker)) 276 .spawn(task1(other_trace.clone(), waker).unwrap());
243 .unwrap();
244 277
245 unsafe { other_executor.poll() }; // just run to the yield_now 278 unsafe { other_executor.poll() }; // just run to the yield_now
246 waker.wake(); // trigger old waker registration 279 waker.wake(); // trigger old waker registration
@@ -283,3 +316,43 @@ fn executor_task_cfg_args() {
283 let (_, _, _) = (a, b, c); 316 let (_, _, _) = (a, b, c);
284 } 317 }
285} 318}
319
320#[test]
321fn recursive_task() {
322 #[embassy_executor::task(pool_size = 2)]
323 async fn task1() {
324 let spawner = unsafe { Spawner::for_current_executor().await };
325 spawner.spawn(task1().unwrap());
326 }
327}
328
329#[cfg(feature = "metadata-name")]
330#[test]
331fn task_metadata() {
332 #[task]
333 async fn task1(expected_name: Option<&'static str>) {
334 use embassy_executor::Metadata;
335 assert_eq!(Metadata::for_current_task().await.name(), expected_name);
336 }
337
338 // check no task name
339 let (executor, _) = setup();
340 executor.spawner().spawn(task1(None).unwrap());
341 unsafe { executor.poll() };
342
343 // check setting task name
344 let token = task1(Some("foo")).unwrap();
345 token.metadata().set_name("foo");
346 executor.spawner().spawn(token);
347 unsafe { executor.poll() };
348
349 let token = task1(Some("bar")).unwrap();
350 token.metadata().set_name("bar");
351 executor.spawner().spawn(token);
352 unsafe { executor.poll() };
353
354 // check name is cleared if the task pool slot is recycled.
355 let (executor, _) = setup();
356 executor.spawner().spawn(task1(None).unwrap());
357 unsafe { executor.poll() };
358}
diff --git a/embassy-executor/tests/ui.rs b/embassy-executor/tests/ui.rs
index 278a4b903..5486a0624 100644
--- a/embassy-executor/tests/ui.rs
+++ b/embassy-executor/tests/ui.rs
@@ -17,8 +17,22 @@ fn ui() {
17 t.compile_fail("tests/ui/nonstatic_struct_elided.rs"); 17 t.compile_fail("tests/ui/nonstatic_struct_elided.rs");
18 t.compile_fail("tests/ui/nonstatic_struct_generic.rs"); 18 t.compile_fail("tests/ui/nonstatic_struct_generic.rs");
19 t.compile_fail("tests/ui/not_async.rs"); 19 t.compile_fail("tests/ui/not_async.rs");
20 t.compile_fail("tests/ui/spawn_nonsend.rs");
21 t.compile_fail("tests/ui/return_impl_future_nonsend.rs");
22 if rustversion::cfg!(stable) {
23 // output is slightly different on nightly
24 t.compile_fail("tests/ui/bad_return_impl_future.rs");
25 t.compile_fail("tests/ui/return_impl_send.rs");
26 }
27 if cfg!(feature = "nightly") {
28 t.compile_fail("tests/ui/bad_return_impl_future_nightly.rs");
29 t.compile_fail("tests/ui/return_impl_send_nightly.rs");
30 }
20 t.compile_fail("tests/ui/self_ref.rs"); 31 t.compile_fail("tests/ui/self_ref.rs");
21 t.compile_fail("tests/ui/self.rs"); 32 t.compile_fail("tests/ui/self.rs");
22 t.compile_fail("tests/ui/type_error.rs"); 33 t.compile_fail("tests/ui/type_error.rs");
23 t.compile_fail("tests/ui/where_clause.rs"); 34 t.compile_fail("tests/ui/where_clause.rs");
35 t.compile_fail("tests/ui/unsafe_op_in_unsafe_task.rs");
36
37 t.pass("tests/ui/task_safety_attribute.rs");
24} 38}
diff --git a/embassy-executor/tests/ui/bad_return_impl_future.rs b/embassy-executor/tests/ui/bad_return_impl_future.rs
new file mode 100644
index 000000000..baaa7dc5a
--- /dev/null
+++ b/embassy-executor/tests/ui/bad_return_impl_future.rs
@@ -0,0 +1,9 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2use core::future::Future;
3
4#[embassy_executor::task]
5fn task() -> impl Future<Output = u32> {
6 async { 5 }
7}
8
9fn main() {}
diff --git a/embassy-executor/tests/ui/bad_return_impl_future.stderr b/embassy-executor/tests/ui/bad_return_impl_future.stderr
new file mode 100644
index 000000000..57f147714
--- /dev/null
+++ b/embassy-executor/tests/ui/bad_return_impl_future.stderr
@@ -0,0 +1,120 @@
1error[E0277]: task futures must resolve to `()` or `!`
2 --> tests/ui/bad_return_impl_future.rs:5:4
3 |
44 | #[embassy_executor::task]
5 | ------------------------- required by a bound introduced by this call
65 | fn task() -> impl Future<Output = u32> {
7 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
8 |
9 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
10note: required by a bound in `task_pool_size`
11 --> src/lib.rs
12 |
13 | pub const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
14 | -------------- required by a bound in this function
15 | where
16 | F: TaskFn<Args, Fut = Fut>,
17 | ^^^^^^^^^ required by this bound in `task_pool_size`
18
19error[E0277]: task futures must resolve to `()` or `!`
20 --> tests/ui/bad_return_impl_future.rs:4:1
21 |
224 | #[embassy_executor::task]
23 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
24 |
25 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
26note: required by a bound in `task_pool_size`
27 --> src/lib.rs
28 |
29 | pub const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
30 | -------------- required by a bound in this function
31 | where
32 | F: TaskFn<Args, Fut = Fut>,
33 | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_size`
34 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
35
36error[E0277]: task futures must resolve to `()` or `!`
37 --> tests/ui/bad_return_impl_future.rs:5:4
38 |
394 | #[embassy_executor::task]
40 | ------------------------- required by a bound introduced by this call
415 | fn task() -> impl Future<Output = u32> {
42 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
43 |
44 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
45note: required by a bound in `task_pool_align`
46 --> src/lib.rs
47 |
48 | pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
49 | --------------- required by a bound in this function
50 | where
51 | F: TaskFn<Args, Fut = Fut>,
52 | ^^^^^^^^^ required by this bound in `task_pool_align`
53
54error[E0277]: task futures must resolve to `()` or `!`
55 --> tests/ui/bad_return_impl_future.rs:4:1
56 |
574 | #[embassy_executor::task]
58 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
59 |
60 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
61note: required by a bound in `task_pool_align`
62 --> src/lib.rs
63 |
64 | pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
65 | --------------- required by a bound in this function
66 | where
67 | F: TaskFn<Args, Fut = Fut>,
68 | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_align`
69 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
70
71error[E0277]: task futures must resolve to `()` or `!`
72 --> tests/ui/bad_return_impl_future.rs:5:4
73 |
744 | #[embassy_executor::task]
75 | ------------------------- required by a bound introduced by this call
765 | fn task() -> impl Future<Output = u32> {
77 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
78 |
79 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
80note: required by a bound in `__task_pool_get`
81 --> tests/ui/bad_return_impl_future.rs:4:1
82 |
834 | #[embassy_executor::task]
84 | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__task_pool_get`
85 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
86
87error[E0277]: task futures must resolve to `()` or `!`
88 --> tests/ui/bad_return_impl_future.rs:5:4
89 |
904 | #[embassy_executor::task]
91 | ------------------------- required by a bound introduced by this call
925 | fn task() -> impl Future<Output = u32> {
93 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
94 |
95 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
96note: required by a bound in `task_pool_new`
97 --> src/lib.rs
98 |
99 | pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE>
100 | ------------- required by a bound in this function
101 | where
102 | F: TaskFn<Args, Fut = Fut>,
103 | ^^^^^^^^^ required by this bound in `task_pool_new`
104
105error[E0277]: task futures must resolve to `()` or `!`
106 --> tests/ui/bad_return_impl_future.rs:4:1
107 |
1084 | #[embassy_executor::task]
109 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Future<Output = u32> {__task_task}`
110 |
111 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
112note: required by a bound in `task_pool_new`
113 --> src/lib.rs
114 |
115 | pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE>
116 | ------------- required by a bound in this function
117 | where
118 | F: TaskFn<Args, Fut = Fut>,
119 | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_new`
120 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/embassy-executor/tests/ui/bad_return_impl_future_nightly.rs b/embassy-executor/tests/ui/bad_return_impl_future_nightly.rs
new file mode 100644
index 000000000..baaa7dc5a
--- /dev/null
+++ b/embassy-executor/tests/ui/bad_return_impl_future_nightly.rs
@@ -0,0 +1,9 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2use core::future::Future;
3
4#[embassy_executor::task]
5fn task() -> impl Future<Output = u32> {
6 async { 5 }
7}
8
9fn main() {}
diff --git a/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr b/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr
new file mode 100644
index 000000000..3c3c9503b
--- /dev/null
+++ b/embassy-executor/tests/ui/bad_return_impl_future_nightly.stderr
@@ -0,0 +1,10 @@
1error[E0277]: task futures must resolve to `()` or `!`
2 --> tests/ui/bad_return_impl_future_nightly.rs:4:1
3 |
44 | #[embassy_executor::task]
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskReturnValue` is not implemented for `u32`
6 |
7 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
8 = help: the following other types implement trait `TaskReturnValue`:
9 ()
10 <fn() -> ! as HasOutput>::Output
diff --git a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr
index 099ef8b4e..0ee1bfe0c 100644
--- a/embassy-executor/tests/ui/nonstatic_struct_elided.stderr
+++ b/embassy-executor/tests/ui/nonstatic_struct_elided.stderr
@@ -8,3 +8,17 @@ help: indicate the anonymous lifetime
8 | 8 |
96 | async fn task(_x: Foo<'_>) {} 96 | async fn task(_x: Foo<'_>) {}
10 | ++++ 10 | ++++
11
12error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
13 --> tests/ui/nonstatic_struct_elided.rs:5:1
14 |
155 | #[embassy_executor::task]
16 | ^^^^^^^^^^^^^^^^^^^^^^^^^ opaque type defined here
176 | async fn task(_x: Foo) {}
18 | --- hidden type `impl Sized` captures the anonymous lifetime defined here
19 |
20 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
21help: add a `use<...>` bound to explicitly capture `'_`
22 |
235 | #[embassy_executor::task] + use<'_>
24 | +++++++++
diff --git a/embassy-executor/tests/ui/return_impl_future_nonsend.rs b/embassy-executor/tests/ui/return_impl_future_nonsend.rs
new file mode 100644
index 000000000..77b3119d6
--- /dev/null
+++ b/embassy-executor/tests/ui/return_impl_future_nonsend.rs
@@ -0,0 +1,21 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2
3use core::future::Future;
4
5use embassy_executor::SendSpawner;
6
7#[embassy_executor::task]
8fn task() -> impl Future<Output = ()> {
9 // runs in spawning thread
10 let non_send: *mut () = core::ptr::null_mut();
11 async move {
12 // runs in executor thread
13 println!("{}", non_send as usize);
14 }
15}
16
17fn send_spawn(s: SendSpawner) {
18 s.spawn(task().unwrap());
19}
20
21fn main() {}
diff --git a/embassy-executor/tests/ui/return_impl_future_nonsend.stderr b/embassy-executor/tests/ui/return_impl_future_nonsend.stderr
new file mode 100644
index 000000000..51944ad65
--- /dev/null
+++ b/embassy-executor/tests/ui/return_impl_future_nonsend.stderr
@@ -0,0 +1,17 @@
1error: future cannot be sent between threads safely
2 --> tests/ui/return_impl_future_nonsend.rs:18:13
3 |
418 | s.spawn(task().unwrap());
5 | ^^^^^^^^^^^^^^^ future created by async block is not `Send`
6 |
7 = help: within `impl Sized`, the trait `Send` is not implemented for `*mut ()`
8note: captured value is not `Send`
9 --> tests/ui/return_impl_future_nonsend.rs:13:24
10 |
1113 | println!("{}", non_send as usize);
12 | ^^^^^^^^ has type `*mut ()` which is not `Send`
13note: required by a bound in `SendSpawner::spawn`
14 --> src/spawner.rs
15 |
16 | pub fn spawn<S: Send>(&self, token: SpawnToken<S>) {
17 | ^^^^ required by this bound in `SendSpawner::spawn`
diff --git a/embassy-executor/tests/ui/return_impl_send.rs b/embassy-executor/tests/ui/return_impl_send.rs
new file mode 100644
index 000000000..6ddb0e722
--- /dev/null
+++ b/embassy-executor/tests/ui/return_impl_send.rs
@@ -0,0 +1,6 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2
3#[embassy_executor::task]
4fn task() -> impl Send {}
5
6fn main() {}
diff --git a/embassy-executor/tests/ui/return_impl_send.stderr b/embassy-executor/tests/ui/return_impl_send.stderr
new file mode 100644
index 000000000..5d19465ec
--- /dev/null
+++ b/embassy-executor/tests/ui/return_impl_send.stderr
@@ -0,0 +1,137 @@
1error[E0277]: task futures must resolve to `()` or `!`
2 --> tests/ui/return_impl_send.rs:4:4
3 |
43 | #[embassy_executor::task]
5 | ------------------------- required by a bound introduced by this call
64 | fn task() -> impl Send {}
7 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
8 |
9 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
10note: required by a bound in `task_pool_size`
11 --> src/lib.rs
12 |
13 | pub const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
14 | -------------- required by a bound in this function
15 | where
16 | F: TaskFn<Args, Fut = Fut>,
17 | ^^^^^^^^^ required by this bound in `task_pool_size`
18
19error[E0277]: task futures must resolve to `()` or `!`
20 --> tests/ui/return_impl_send.rs:3:1
21 |
223 | #[embassy_executor::task]
23 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
24 |
25 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
26note: required by a bound in `task_pool_size`
27 --> src/lib.rs
28 |
29 | pub const fn task_pool_size<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
30 | -------------- required by a bound in this function
31 | where
32 | F: TaskFn<Args, Fut = Fut>,
33 | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_size`
34 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
35
36error[E0277]: task futures must resolve to `()` or `!`
37 --> tests/ui/return_impl_send.rs:4:4
38 |
393 | #[embassy_executor::task]
40 | ------------------------- required by a bound introduced by this call
414 | fn task() -> impl Send {}
42 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
43 |
44 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
45note: required by a bound in `task_pool_align`
46 --> src/lib.rs
47 |
48 | pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
49 | --------------- required by a bound in this function
50 | where
51 | F: TaskFn<Args, Fut = Fut>,
52 | ^^^^^^^^^ required by this bound in `task_pool_align`
53
54error[E0277]: task futures must resolve to `()` or `!`
55 --> tests/ui/return_impl_send.rs:3:1
56 |
573 | #[embassy_executor::task]
58 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
59 |
60 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
61note: required by a bound in `task_pool_align`
62 --> src/lib.rs
63 |
64 | pub const fn task_pool_align<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> usize
65 | --------------- required by a bound in this function
66 | where
67 | F: TaskFn<Args, Fut = Fut>,
68 | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_align`
69 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
70
71error[E0277]: task futures must resolve to `()` or `!`
72 --> tests/ui/return_impl_send.rs:4:4
73 |
743 | #[embassy_executor::task]
75 | ------------------------- required by a bound introduced by this call
764 | fn task() -> impl Send {}
77 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
78 |
79 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
80note: required by a bound in `__task_pool_get`
81 --> tests/ui/return_impl_send.rs:3:1
82 |
833 | #[embassy_executor::task]
84 | ^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `__task_pool_get`
85 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
86
87error[E0277]: `impl Send` is not a future
88 --> tests/ui/return_impl_send.rs:3:1
89 |
903 | #[embassy_executor::task]
91 | ^^^^^^^^^^^^^^^^^^^^^^^^^ `impl Send` is not a future
92 |
93 = help: the trait `Future` is not implemented for `impl Send`
94note: required by a bound in `TaskPool::<F, N>::spawn`
95 --> src/raw/mod.rs
96 |
97 | impl<F: Future + 'static, const N: usize> TaskPool<F, N> {
98 | ^^^^^^ required by this bound in `TaskPool::<F, N>::spawn`
99...
100 | pub fn spawn(&'static self, future: impl FnOnce() -> F) -> Result<SpawnToken<impl Sized>, SpawnError> {
101 | ----- required by a bound in this associated function
102 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
103
104error[E0277]: task futures must resolve to `()` or `!`
105 --> tests/ui/return_impl_send.rs:4:4
106 |
1073 | #[embassy_executor::task]
108 | ------------------------- required by a bound introduced by this call
1094 | fn task() -> impl Send {}
110 | ^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
111 |
112 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
113note: required by a bound in `task_pool_new`
114 --> src/lib.rs
115 |
116 | pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE>
117 | ------------- required by a bound in this function
118 | where
119 | F: TaskFn<Args, Fut = Fut>,
120 | ^^^^^^^^^ required by this bound in `task_pool_new`
121
122error[E0277]: task futures must resolve to `()` or `!`
123 --> tests/ui/return_impl_send.rs:3:1
124 |
1253 | #[embassy_executor::task]
126 | ^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `TaskFn<_>` is not implemented for fn item `fn() -> impl Send {__task_task}`
127 |
128 = note: use `async fn` or change the return type to `impl Future<Output = ()>`
129note: required by a bound in `task_pool_new`
130 --> src/lib.rs
131 |
132 | pub const fn task_pool_new<F, Args, Fut, const POOL_SIZE: usize>(_: F) -> TaskPool<Fut, POOL_SIZE>
133 | ------------- required by a bound in this function
134 | where
135 | F: TaskFn<Args, Fut = Fut>,
136 | ^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `task_pool_new`
137 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/embassy-executor/tests/ui/return_impl_send_nightly.rs b/embassy-executor/tests/ui/return_impl_send_nightly.rs
new file mode 100644
index 000000000..6ddb0e722
--- /dev/null
+++ b/embassy-executor/tests/ui/return_impl_send_nightly.rs
@@ -0,0 +1,6 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2
3#[embassy_executor::task]
4fn task() -> impl Send {}
5
6fn main() {}
diff --git a/embassy-executor/tests/ui/return_impl_send_nightly.stderr b/embassy-executor/tests/ui/return_impl_send_nightly.stderr
new file mode 100644
index 000000000..de9ba6243
--- /dev/null
+++ b/embassy-executor/tests/ui/return_impl_send_nightly.stderr
@@ -0,0 +1,10 @@
1error[E0277]: `impl Send` is not a future
2 --> tests/ui/return_impl_send_nightly.rs:3:1
3 |
43 | #[embassy_executor::task]
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^
6 | |
7 | `impl Send` is not a future
8 | return type was inferred to be `impl Send` here
9 |
10 = help: the trait `Future` is not implemented for `impl Send`
diff --git a/embassy-executor/tests/ui/spawn_nonsend.rs b/embassy-executor/tests/ui/spawn_nonsend.rs
new file mode 100644
index 000000000..601041941
--- /dev/null
+++ b/embassy-executor/tests/ui/spawn_nonsend.rs
@@ -0,0 +1,16 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2
3use core::future::Future;
4
5use embassy_executor::SendSpawner;
6
7#[embassy_executor::task]
8async fn task(non_send: *mut ()) {
9 println!("{}", non_send as usize);
10}
11
12fn send_spawn(s: SendSpawner) {
13 s.spawn(task(core::ptr::null_mut()).unwrap());
14}
15
16fn main() {}
diff --git a/embassy-executor/tests/ui/spawn_nonsend.stderr b/embassy-executor/tests/ui/spawn_nonsend.stderr
new file mode 100644
index 000000000..25bd7d78d
--- /dev/null
+++ b/embassy-executor/tests/ui/spawn_nonsend.stderr
@@ -0,0 +1,41 @@
1warning: unused import: `core::future::Future`
2 --> tests/ui/spawn_nonsend.rs:3:5
3 |
43 | use core::future::Future;
5 | ^^^^^^^^^^^^^^^^^^^^
6 |
7 = note: `#[warn(unused_imports)]` on by default
8
9error[E0277]: `*mut ()` cannot be sent between threads safely
10 --> tests/ui/spawn_nonsend.rs:13:13
11 |
127 | #[embassy_executor::task]
13 | ------------------------- within this `impl Sized`
14...
1513 | s.spawn(task(core::ptr::null_mut()).unwrap());
16 | ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `*mut ()` cannot be sent between threads safely
17 | |
18 | required by a bound introduced by this call
19 |
20 = help: within `impl Sized`, the trait `Send` is not implemented for `*mut ()`
21note: required because it's used within this closure
22 --> tests/ui/spawn_nonsend.rs:7:1
23 |
247 | #[embassy_executor::task]
25 | ^^^^^^^^^^^^^^^^^^^^^^^^^
26note: required because it appears within the type `impl Sized`
27 --> src/raw/mod.rs
28 |
29 | pub unsafe fn _spawn_async_fn<FutFn>(&'static self, future: FutFn) -> Result<SpawnToken<impl Sized>, SpawnError>
30 | ^^^^^^^^^^
31note: required because it appears within the type `impl Sized`
32 --> tests/ui/spawn_nonsend.rs:7:1
33 |
347 | #[embassy_executor::task]
35 | ^^^^^^^^^^^^^^^^^^^^^^^^^
36note: required by a bound in `SendSpawner::spawn`
37 --> src/spawner.rs
38 |
39 | pub fn spawn<S: Send>(&self, token: SpawnToken<S>) {
40 | ^^^^ required by this bound in `SendSpawner::spawn`
41 = note: this error originates in the attribute macro `embassy_executor::task` (in Nightly builds, run with -Z macro-backtrace for more info)
diff --git a/embassy-executor/tests/ui/task_safety_attribute.rs b/embassy-executor/tests/ui/task_safety_attribute.rs
new file mode 100644
index 000000000..ab5a2f99f
--- /dev/null
+++ b/embassy-executor/tests/ui/task_safety_attribute.rs
@@ -0,0 +1,25 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2#![deny(unused_unsafe)]
3
4use std::mem;
5
6#[embassy_executor::task]
7async fn safe() {}
8
9#[embassy_executor::task]
10async unsafe fn not_safe() {}
11
12#[export_name = "__pender"]
13fn pender(_: *mut ()) {
14 // The test doesn't link if we don't include this.
15 // We never call this anyway.
16}
17
18fn main() {
19 let _forget_me = safe();
20 // SAFETY: not_safe has not safety preconditions
21 let _forget_me2 = unsafe { not_safe() };
22
23 mem::forget(_forget_me);
24 mem::forget(_forget_me2);
25}
diff --git a/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs
new file mode 100644
index 000000000..ee7924838
--- /dev/null
+++ b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.rs
@@ -0,0 +1,10 @@
1#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
2#![deny(unsafe_op_in_unsafe_fn)]
3
4#[embassy_executor::task]
5async unsafe fn task() {
6 let x = 5;
7 (&x as *const i32).read();
8}
9
10fn main() {}
diff --git a/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr
new file mode 100644
index 000000000..d987a4b95
--- /dev/null
+++ b/embassy-executor/tests/ui/unsafe_op_in_unsafe_task.stderr
@@ -0,0 +1,18 @@
1error[E0133]: call to unsafe function `std::ptr::const_ptr::<impl *const T>::read` is unsafe and requires unsafe block
2 --> tests/ui/unsafe_op_in_unsafe_task.rs:7:5
3 |
47 | (&x as *const i32).read();
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function
6 |
7 = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html>
8 = note: consult the function's documentation for information on how to avoid undefined behavior
9note: an unsafe function restricts its caller, but its body is safe by default
10 --> tests/ui/unsafe_op_in_unsafe_task.rs:5:1
11 |
125 | async unsafe fn task() {
13 | ^^^^^^^^^^^^^^^^^^^^^^
14note: the lint level is defined here
15 --> tests/ui/unsafe_op_in_unsafe_task.rs:2:9
16 |
172 | #![deny(unsafe_op_in_unsafe_fn)]
18 | ^^^^^^^^^^^^^^^^^^^^^^