aboutsummaryrefslogtreecommitdiff
path: root/embassy-executor
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2023-11-24 22:57:46 +0000
committerGitHub <[email protected]>2023-11-24 22:57:46 +0000
commit47bac9df70492cd9c1b1f8617c1abc1209ee3338 (patch)
tree2915a74fde18bdca6fa4d4e98739e656de73a15c /embassy-executor
parent78f8e6112a506633a0b2dcaba9434f376256b77c (diff)
parent996c3c1f7e389b1e7d26ca6f02524fff3d63212e (diff)
Merge pull request #2216 from embassy-rs/stable
executor: add support for main/task macros in stable Rust
Diffstat (limited to 'embassy-executor')
-rw-r--r--embassy-executor/Cargo.toml90
-rw-r--r--embassy-executor/README.md28
-rw-r--r--embassy-executor/build.rs93
-rw-r--r--embassy-executor/gen_config.py82
-rw-r--r--embassy-executor/src/arch/cortex_m.rs1
-rw-r--r--embassy-executor/src/arch/riscv32.rs1
-rw-r--r--embassy-executor/src/arch/std.rs1
-rw-r--r--embassy-executor/src/arch/wasm.rs1
-rw-r--r--embassy-executor/src/arch/xtensa.rs1
-rw-r--r--embassy-executor/src/lib.rs110
-rw-r--r--embassy-executor/tests/test.rs137
11 files changed, 497 insertions, 48 deletions
diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml
index 3a623e99f..ae46b17c6 100644
--- a/embassy-executor/Cargo.toml
+++ b/embassy-executor/Cargo.toml
@@ -27,6 +27,30 @@ default-target = "thumbv7em-none-eabi"
27targets = ["thumbv7em-none-eabi"] 27targets = ["thumbv7em-none-eabi"]
28features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"] 28features = ["nightly", "defmt", "arch-cortex-m", "executor-thread", "executor-interrupt"]
29 29
30[dependencies]
31defmt = { version = "0.3", optional = true }
32log = { version = "0.4.14", optional = true }
33rtos-trace = { version = "0.1.2", optional = true }
34
35embassy-macros = { version = "0.2.1", path = "../embassy-macros" }
36embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true}
37critical-section = "1.1"
38
39# needed for riscv
40# remove when https://github.com/rust-lang/rust/pull/114499 is merged
41portable-atomic = { version = "1.5", optional = true }
42
43# arch-cortex-m dependencies
44cortex-m = { version = "0.7.6", optional = true }
45
46# arch-wasm dependencies
47wasm-bindgen = { version = "0.2.82", optional = true }
48js-sys = { version = "0.3", optional = true }
49
50[dev-dependencies]
51critical-section = { version = "1.1", features = ["std"] }
52
53
30[features] 54[features]
31 55
32# Architecture 56# Architecture
@@ -43,31 +67,53 @@ executor-thread = []
43executor-interrupt = [] 67executor-interrupt = []
44 68
45# Enable nightly-only features 69# Enable nightly-only features
46nightly = [] 70nightly = ["embassy-macros/nightly"]
47 71
48turbowakers = [] 72turbowakers = []
49 73
50integrated-timers = ["dep:embassy-time"] 74integrated-timers = ["dep:embassy-time"]
51 75
52# Trace interrupt invocations with rtos-trace. 76# BEGIN AUTOGENERATED CONFIG FEATURES
53rtos-trace-interrupt = ["rtos-trace", "embassy-macros/rtos-trace-interrupt"] 77# Generated by gen_config.py. DO NOT EDIT.
54 78task-arena-size-64 = []
55[dependencies] 79task-arena-size-128 = []
56defmt = { version = "0.3", optional = true } 80task-arena-size-192 = []
57log = { version = "0.4.14", optional = true } 81task-arena-size-256 = []
58rtos-trace = { version = "0.1.2", optional = true } 82task-arena-size-320 = []
59 83task-arena-size-384 = []
60embassy-macros = { version = "0.2.1", path = "../embassy-macros" } 84task-arena-size-512 = []
61embassy-time = { version = "0.1.5", path = "../embassy-time", optional = true} 85task-arena-size-640 = []
62critical-section = "1.1" 86task-arena-size-768 = []
63 87task-arena-size-1024 = []
64# needed for riscv 88task-arena-size-1280 = []
65# remove when https://github.com/rust-lang/rust/pull/114499 is merged 89task-arena-size-1536 = []
66portable-atomic = { version = "1.5", optional = true } 90task-arena-size-2048 = []
67 91task-arena-size-2560 = []
68# arch-cortex-m dependencies 92task-arena-size-3072 = []
69cortex-m = { version = "0.7.6", optional = true } 93task-arena-size-4096 = [] # Default
94task-arena-size-5120 = []
95task-arena-size-6144 = []
96task-arena-size-8192 = []
97task-arena-size-10240 = []
98task-arena-size-12288 = []
99task-arena-size-16384 = []
100task-arena-size-20480 = []
101task-arena-size-24576 = []
102task-arena-size-32768 = []
103task-arena-size-40960 = []
104task-arena-size-49152 = []
105task-arena-size-65536 = []
106task-arena-size-81920 = []
107task-arena-size-98304 = []
108task-arena-size-131072 = []
109task-arena-size-163840 = []
110task-arena-size-196608 = []
111task-arena-size-262144 = []
112task-arena-size-327680 = []
113task-arena-size-393216 = []
114task-arena-size-524288 = []
115task-arena-size-655360 = []
116task-arena-size-786432 = []
117task-arena-size-1048576 = []
70 118
71# arch-wasm dependencies 119# END AUTOGENERATED CONFIG FEATURES
72wasm-bindgen = { version = "0.2.82", optional = true }
73js-sys = { version = "0.3", optional = true }
diff --git a/embassy-executor/README.md b/embassy-executor/README.md
index 3c1448a18..80ecfc71a 100644
--- a/embassy-executor/README.md
+++ b/embassy-executor/README.md
@@ -2,10 +2,36 @@
2 2
3An async/await executor designed for embedded usage. 3An async/await executor designed for embedded usage.
4 4
5- No `alloc`, no heap needed. Task futures are statically allocated. 5- No `alloc`, no heap needed.
6- With nightly Rust, task futures can be fully statically allocated.
6- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning. 7- No "fixed capacity" data structures, executor works with 1 or 1000 tasks without needing config/tuning.
7- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`. 8- Integrated timer queue: sleeping is easy, just do `Timer::after_secs(1).await;`.
8- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`. 9- No busy-loop polling: CPU sleeps when there's no work to do, using interrupts or `WFE/SEV`.
9- Efficient polling: a wake will only poll the woken task, not all of them. 10- Efficient polling: a wake will only poll the woken task, not all of them.
10- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time. 11- Fair: a task can't monopolize CPU time even if it's constantly being woken. All other tasks get a chance to run before a given task gets polled for the second time.
11- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks. 12- Creating multiple executor instances is supported, to run tasks with multiple priority levels. This allows higher-priority tasks to preempt lower-priority tasks.
13
14## Task arena
15
16When the `nightly` Cargo feature is not enabled, `embassy-executor` allocates tasks out of an arena (a very simple bump allocator).
17
18If the task arena gets full, the program will panic at runtime. To guarantee this doesn't happen, you must set the size to the sum of sizes of all tasks.
19
20Tasks are allocated from the arena when spawned for the first time. If the task exists, the allocation is not released to the arena, but can be reused to spawn the task again. For multiple-instance tasks (like `#[embassy_executor::task(pool_size = 4)]`), the first spawn will allocate memory for all instances. This is done for performance and to increase predictability (for example, spawning at least 1 instance of every task at boot guarantees an immediate panic if the arena is too small, while allocating instances on-demand could delay the panic to only when the program is under load).
21
22The arena size can be configured in two ways:
23
24- Via Cargo features: enable a Cargo feature like `task-arena-size-8192`. Only a selection of values
25 is available, check `Cargo.toml` for the list.
26- Via environment variables at build time: set the variable named `EMBASSY_EXECUTOR_TASK_ARENA_SIZE`. For example
27 `EMBASSY_EXECUTOR_TASK_ARENA_SIZE=4321 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`.
28 Any value can be set, unlike with Cargo features.
29
30Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting
31with different values, compilation fails.
32
33## Statically allocating tasks
34
35When using nightly Rust, enable the `nightly` Cargo feature. This will make `embassy-executor` use the `type_alias_impl_trait` feature to allocate all tasks in `static`s. Each task gets its own `static`, with the exact size to hold the task (or multiple instances of it, if using `pool_size`) calculated automatically at compile time. If tasks don't fit in RAM, this is detected at compile time by the linker. Runtime panics due to running out of memory are not possible.
36
37The configured arena size is ignored, no arena is used at all.
diff --git a/embassy-executor/build.rs b/embassy-executor/build.rs
index 6fe82b44f..07f31e3fb 100644
--- a/embassy-executor/build.rs
+++ b/embassy-executor/build.rs
@@ -1,6 +1,97 @@
1use std::env; 1use std::collections::HashMap;
2use std::fmt::Write;
3use std::path::PathBuf;
4use std::{env, fs};
5
6static CONFIGS: &[(&str, usize)] = &[
7 // BEGIN AUTOGENERATED CONFIG FEATURES
8 // Generated by gen_config.py. DO NOT EDIT.
9 ("TASK_ARENA_SIZE", 4096),
10 // END AUTOGENERATED CONFIG FEATURES
11];
12
13struct ConfigState {
14 value: usize,
15 seen_feature: bool,
16 seen_env: bool,
17}
2 18
3fn main() { 19fn main() {
20 let crate_name = env::var("CARGO_PKG_NAME")
21 .unwrap()
22 .to_ascii_uppercase()
23 .replace('-', "_");
24
25 // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any
26 // other file changed.
27 println!("cargo:rerun-if-changed=build.rs");
28
29 // Rebuild if config envvar changed.
30 for (name, _) in CONFIGS {
31 println!("cargo:rerun-if-env-changed={crate_name}_{name}");
32 }
33
34 let mut configs = HashMap::new();
35 for (name, default) in CONFIGS {
36 configs.insert(
37 *name,
38 ConfigState {
39 value: *default,
40 seen_env: false,
41 seen_feature: false,
42 },
43 );
44 }
45
46 let prefix = format!("{crate_name}_");
47 for (var, value) in env::vars() {
48 if let Some(name) = var.strip_prefix(&prefix) {
49 let Some(cfg) = configs.get_mut(name) else {
50 panic!("Unknown env var {name}")
51 };
52
53 let Ok(value) = value.parse::<usize>() else {
54 panic!("Invalid value for env var {name}: {value}")
55 };
56
57 cfg.value = value;
58 cfg.seen_env = true;
59 }
60
61 if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") {
62 if let Some(i) = feature.rfind('_') {
63 let name = &feature[..i];
64 let value = &feature[i + 1..];
65 if let Some(cfg) = configs.get_mut(name) {
66 let Ok(value) = value.parse::<usize>() else {
67 panic!("Invalid value for feature {name}: {value}")
68 };
69
70 // envvars take priority.
71 if !cfg.seen_env {
72 if cfg.seen_feature {
73 panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value);
74 }
75
76 cfg.value = value;
77 cfg.seen_feature = true;
78 }
79 }
80 }
81 }
82 }
83
84 let mut data = String::new();
85
86 for (name, cfg) in &configs {
87 writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap();
88 }
89
90 let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
91 let out_file = out_dir.join("config.rs").to_string_lossy().to_string();
92 fs::write(out_file, data).unwrap();
93
94 // cortex-m targets
4 let target = env::var("TARGET").unwrap(); 95 let target = env::var("TARGET").unwrap();
5 96
6 if target.starts_with("thumbv6m-") { 97 if target.starts_with("thumbv6m-") {
diff --git a/embassy-executor/gen_config.py b/embassy-executor/gen_config.py
new file mode 100644
index 000000000..e427d29f4
--- /dev/null
+++ b/embassy-executor/gen_config.py
@@ -0,0 +1,82 @@
1import os
2
3abspath = os.path.abspath(__file__)
4dname = os.path.dirname(abspath)
5os.chdir(dname)
6
7features = []
8
9
10def feature(name, default, min=None, max=None, pow2=None, vals=None, factors=[]):
11 if vals is None:
12 assert min is not None
13 assert max is not None
14
15 vals = set()
16 val = min
17 while val <= max:
18 vals.add(val)
19 for f in factors:
20 if val * f <= max:
21 vals.add(val * f)
22 if (pow2 == True or (isinstance(pow2, int) and val >= pow2)) and val > 0:
23 val *= 2
24 else:
25 val += 1
26 vals.add(default)
27 vals = sorted(list(vals))
28
29 features.append(
30 {
31 "name": name,
32 "default": default,
33 "vals": vals,
34 }
35 )
36
37
38feature(
39 "task_arena_size", default=4096, min=64, max=1024 * 1024, pow2=True, factors=[3, 5]
40)
41
42# ========= Update Cargo.toml
43
44things = ""
45for f in features:
46 name = f["name"].replace("_", "-")
47 for val in f["vals"]:
48 things += f"{name}-{val} = []"
49 if val == f["default"]:
50 things += " # Default"
51 things += "\n"
52 things += "\n"
53
54SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n"
55SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n"
56HELP = "# Generated by gen_config.py. DO NOT EDIT.\n"
57with open("Cargo.toml", "r") as f:
58 data = f.read()
59before, data = data.split(SEPARATOR_START, maxsplit=1)
60_, after = data.split(SEPARATOR_END, maxsplit=1)
61data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after
62with open("Cargo.toml", "w") as f:
63 f.write(data)
64
65
66# ========= Update build.rs
67
68things = ""
69for f in features:
70 name = f["name"].upper()
71 things += f' ("{name}", {f["default"]}),\n'
72
73SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n"
74SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n"
75HELP = " // Generated by gen_config.py. DO NOT EDIT.\n"
76with open("build.rs", "r") as f:
77 data = f.read()
78before, data = data.split(SEPARATOR_START, maxsplit=1)
79_, after = data.split(SEPARATOR_END, maxsplit=1)
80data = before + SEPARATOR_START + HELP + things + " " + SEPARATOR_END + after
81with open("build.rs", "w") as f:
82 f.write(data)
diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs
index 55299c94f..4a6d58575 100644
--- a/embassy-executor/src/arch/cortex_m.rs
+++ b/embassy-executor/src/arch/cortex_m.rs
@@ -51,7 +51,6 @@ mod thread {
51 use core::arch::asm; 51 use core::arch::asm;
52 use core::marker::PhantomData; 52 use core::marker::PhantomData;
53 53
54 #[cfg(feature = "nightly")]
55 pub use embassy_macros::main_cortex_m as main; 54 pub use embassy_macros::main_cortex_m as main;
56 55
57 use crate::{raw, Spawner}; 56 use crate::{raw, Spawner};
diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs
index 6814e7844..ca12c3403 100644
--- a/embassy-executor/src/arch/riscv32.rs
+++ b/embassy-executor/src/arch/riscv32.rs
@@ -7,7 +7,6 @@ pub use thread::*;
7mod thread { 7mod thread {
8 use core::marker::PhantomData; 8 use core::marker::PhantomData;
9 9
10 #[cfg(feature = "nightly")]
11 pub use embassy_macros::main_riscv as main; 10 pub use embassy_macros::main_riscv as main;
12 use portable_atomic::{AtomicBool, Ordering}; 11 use portable_atomic::{AtomicBool, Ordering};
13 12
diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs
index 5b2f7e2e4..598bb0509 100644
--- a/embassy-executor/src/arch/std.rs
+++ b/embassy-executor/src/arch/std.rs
@@ -8,7 +8,6 @@ mod thread {
8 use std::marker::PhantomData; 8 use std::marker::PhantomData;
9 use std::sync::{Condvar, Mutex}; 9 use std::sync::{Condvar, Mutex};
10 10
11 #[cfg(feature = "nightly")]
12 pub use embassy_macros::main_std as main; 11 pub use embassy_macros::main_std as main;
13 12
14 use crate::{raw, Spawner}; 13 use crate::{raw, Spawner};
diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs
index 15aed867a..3faa92575 100644
--- a/embassy-executor/src/arch/wasm.rs
+++ b/embassy-executor/src/arch/wasm.rs
@@ -8,7 +8,6 @@ mod thread {
8 8
9 use core::marker::PhantomData; 9 use core::marker::PhantomData;
10 10
11 #[cfg(feature = "nightly")]
12 pub use embassy_macros::main_wasm as main; 11 pub use embassy_macros::main_wasm as main;
13 use js_sys::Promise; 12 use js_sys::Promise;
14 use wasm_bindgen::prelude::*; 13 use wasm_bindgen::prelude::*;
diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs
index d335594e2..6ed9f9e72 100644
--- a/embassy-executor/src/arch/xtensa.rs
+++ b/embassy-executor/src/arch/xtensa.rs
@@ -8,7 +8,6 @@ mod thread {
8 use core::marker::PhantomData; 8 use core::marker::PhantomData;
9 use core::sync::atomic::{AtomicBool, Ordering}; 9 use core::sync::atomic::{AtomicBool, Ordering};
10 10
11 #[cfg(feature = "nightly")]
12 pub use embassy_macros::main_riscv as main; 11 pub use embassy_macros::main_riscv as main;
13 12
14 use crate::{raw, Spawner}; 13 use crate::{raw, Spawner};
diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs
index f2c86d8e6..d8ac4893b 100644
--- a/embassy-executor/src/lib.rs
+++ b/embassy-executor/src/lib.rs
@@ -1,5 +1,5 @@
1#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] 1#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)]
2#![cfg_attr(all(feature = "nightly", feature = "arch-xtensa"), feature(asm_experimental_arch))] 2#![cfg_attr(feature = "arch-xtensa", feature(asm_experimental_arch))]
3#![allow(clippy::new_without_default)] 3#![allow(clippy::new_without_default)]
4#![doc = include_str!("../README.md")] 4#![doc = include_str!("../README.md")]
5#![warn(missing_docs)] 5#![warn(missing_docs)]
@@ -7,7 +7,6 @@
7// This mod MUST go first, so that the others see its macros. 7// This mod MUST go first, so that the others see its macros.
8pub(crate) mod fmt; 8pub(crate) mod fmt;
9 9
10#[cfg(feature = "nightly")]
11pub use embassy_macros::task; 10pub use embassy_macros::task;
12 11
13macro_rules! check_at_most_one { 12macro_rules! check_at_most_one {
@@ -41,28 +40,101 @@ pub mod raw;
41mod spawner; 40mod spawner;
42pub use spawner::*; 41pub use spawner::*;
43 42
43mod config {
44 #![allow(unused)]
45 include!(concat!(env!("OUT_DIR"), "/config.rs"));
46}
47
44/// Implementation details for embassy macros. 48/// Implementation details for embassy macros.
45/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. 49/// Do not use. Used for macros and HALs only. Not covered by semver guarantees.
46#[doc(hidden)] 50#[doc(hidden)]
51#[cfg(not(feature = "nightly"))]
47pub mod _export { 52pub mod _export {
48 #[cfg(feature = "rtos-trace")] 53 use core::alloc::Layout;
49 pub use rtos_trace::trace; 54 use core::cell::{Cell, UnsafeCell};
50 55 use core::future::Future;
51 /// Expands the given block of code when `embassy-executor` is compiled with 56 use core::mem::MaybeUninit;
52 /// the `rtos-trace-interrupt` feature. 57 use core::ptr::null_mut;
53 #[doc(hidden)] 58
54 #[macro_export] 59 use critical_section::{CriticalSection, Mutex};
55 #[cfg(feature = "rtos-trace-interrupt")] 60
56 macro_rules! rtos_trace_interrupt { 61 use crate::raw::TaskPool;
57 ($($tt:tt)*) => { $($tt)* }; 62
63 struct Arena<const N: usize> {
64 buf: UnsafeCell<MaybeUninit<[u8; N]>>,
65 ptr: Mutex<Cell<*mut u8>>,
58 } 66 }
59 67
60 /// Does not expand the given block of code when `embassy-executor` is 68 unsafe impl<const N: usize> Sync for Arena<N> {}
61 /// compiled without the `rtos-trace-interrupt` feature. 69 unsafe impl<const N: usize> Send for Arena<N> {}
62 #[doc(hidden)] 70
63 #[macro_export] 71 impl<const N: usize> Arena<N> {
64 #[cfg(not(feature = "rtos-trace-interrupt"))] 72 const fn new() -> Self {
65 macro_rules! rtos_trace_interrupt { 73 Self {
66 ($($tt:tt)*) => {}; 74 buf: UnsafeCell::new(MaybeUninit::uninit()),
75 ptr: Mutex::new(Cell::new(null_mut())),
76 }
77 }
78
79 fn alloc<T>(&'static self, cs: CriticalSection) -> &'static mut MaybeUninit<T> {
80 let layout = Layout::new::<T>();
81
82 let start = self.buf.get().cast::<u8>();
83 let end = unsafe { start.add(N) };
84
85 let mut ptr = self.ptr.borrow(cs).get();
86 if ptr.is_null() {
87 ptr = self.buf.get().cast::<u8>();
88 }
89
90 let bytes_left = (end as usize) - (ptr as usize);
91 let align_offset = (ptr as usize).next_multiple_of(layout.align()) - (ptr as usize);
92
93 if align_offset + layout.size() > bytes_left {
94 panic!("embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/");
95 }
96
97 let res = unsafe { ptr.add(align_offset) };
98 let ptr = unsafe { ptr.add(align_offset + layout.size()) };
99
100 self.ptr.borrow(cs).set(ptr);
101
102 unsafe { &mut *(res as *mut MaybeUninit<T>) }
103 }
104 }
105
106 static ARENA: Arena<{ crate::config::TASK_ARENA_SIZE }> = Arena::new();
107
108 pub struct TaskPoolRef {
109 // type-erased `&'static mut TaskPool<F, N>`
110 // Needed because statics can't have generics.
111 ptr: Mutex<Cell<*mut ()>>,
112 }
113 unsafe impl Sync for TaskPoolRef {}
114 unsafe impl Send for TaskPoolRef {}
115
116 impl TaskPoolRef {
117 pub const fn new() -> Self {
118 Self {
119 ptr: Mutex::new(Cell::new(null_mut())),
120 }
121 }
122
123 /// Get the pool for this ref, allocating it from the arena the first time.
124 ///
125 /// safety: for a given TaskPoolRef instance, must always call with the exact
126 /// same generic params.
127 pub unsafe fn get<F: Future, const N: usize>(&'static self) -> &'static TaskPool<F, N> {
128 critical_section::with(|cs| {
129 let ptr = self.ptr.borrow(cs);
130 if ptr.get().is_null() {
131 let pool = ARENA.alloc::<TaskPool<F, N>>(cs);
132 pool.write(TaskPool::new());
133 ptr.set(pool as *mut _ as _);
134 }
135
136 unsafe { &*(ptr.get() as *const _) }
137 })
138 }
67 } 139 }
68} 140}
diff --git a/embassy-executor/tests/test.rs b/embassy-executor/tests/test.rs
new file mode 100644
index 000000000..0dbd391e8
--- /dev/null
+++ b/embassy-executor/tests/test.rs
@@ -0,0 +1,137 @@
1#![cfg_attr(feature = "nightly", feature(type_alias_impl_trait))]
2
3use std::boxed::Box;
4use std::future::poll_fn;
5use std::sync::{Arc, Mutex};
6use std::task::Poll;
7
8use embassy_executor::raw::Executor;
9use embassy_executor::task;
10
11#[export_name = "__pender"]
12fn __pender(context: *mut ()) {
13 unsafe {
14 let trace = &*(context as *const Trace);
15 trace.push("pend");
16 }
17}
18
19#[derive(Clone)]
20struct Trace {
21 trace: Arc<Mutex<Vec<&'static str>>>,
22}
23
24impl Trace {
25 fn new() -> Self {
26 Self {
27 trace: Arc::new(Mutex::new(Vec::new())),
28 }
29 }
30 fn push(&self, value: &'static str) {
31 self.trace.lock().unwrap().push(value)
32 }
33
34 fn get(&self) -> Vec<&'static str> {
35 self.trace.lock().unwrap().clone()
36 }
37}
38
39fn setup() -> (&'static Executor, Trace) {
40 let trace = Trace::new();
41 let context = Box::leak(Box::new(trace.clone())) as *mut _ as *mut ();
42 let executor = &*Box::leak(Box::new(Executor::new(context)));
43 (executor, trace)
44}
45
46#[test]
47fn executor_noop() {
48 let (executor, trace) = setup();
49 unsafe { executor.poll() };
50 assert!(trace.get().is_empty())
51}
52
53#[test]
54fn executor_task() {
55 #[task]
56 async fn task1(trace: Trace) {
57 trace.push("poll task1")
58 }
59
60 let (executor, trace) = setup();
61 executor.spawner().spawn(task1(trace.clone())).unwrap();
62
63 unsafe { executor.poll() };
64 unsafe { executor.poll() };
65
66 assert_eq!(
67 trace.get(),
68 &[
69 "pend", // spawning a task pends the executor
70 "poll task1", // poll only once.
71 ]
72 )
73}
74
75#[test]
76fn executor_task_self_wake() {
77 #[task]
78 async fn task1(trace: Trace) {
79 poll_fn(|cx| {
80 trace.push("poll task1");
81 cx.waker().wake_by_ref();
82 Poll::Pending
83 })
84 .await
85 }
86
87 let (executor, trace) = setup();
88 executor.spawner().spawn(task1(trace.clone())).unwrap();
89
90 unsafe { executor.poll() };
91 unsafe { executor.poll() };
92
93 assert_eq!(
94 trace.get(),
95 &[
96 "pend", // spawning a task pends the executor
97 "poll task1", //
98 "pend", // task self-wakes
99 "poll task1", //
100 "pend", // task self-wakes
101 ]
102 )
103}
104
105#[test]
106fn executor_task_self_wake_twice() {
107 #[task]
108 async fn task1(trace: Trace) {
109 poll_fn(|cx| {
110 trace.push("poll task1");
111 cx.waker().wake_by_ref();
112 trace.push("poll task1 wake 2");
113 cx.waker().wake_by_ref();
114 Poll::Pending
115 })
116 .await
117 }
118
119 let (executor, trace) = setup();
120 executor.spawner().spawn(task1(trace.clone())).unwrap();
121
122 unsafe { executor.poll() };
123 unsafe { executor.poll() };
124
125 assert_eq!(
126 trace.get(),
127 &[
128 "pend", // spawning a task pends the executor
129 "poll task1", //
130 "pend", // task self-wakes
131 "poll task1 wake 2", // task self-wakes again, shouldn't pend
132 "poll task1", //
133 "pend", // task self-wakes
134 "poll task1 wake 2", // task self-wakes again, shouldn't pend
135 ]
136 )
137}