diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md index 5fbb8cf13c..568126c32d 100644 --- a/embassy-executor/CHANGELOG.md +++ b/embassy-executor/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added optional "earliest deadline first" EDF scheduling - Migrate `cortex-ar` to `aarch32-cpu`. The feature name `arch-cortex-ar` remains the same and legacy ARM architectures are not supported. +- Added `KillableExecutor` to `arch-std`. ## 0.9.1 - 2025-08-31 diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index c62ab723b0..1083053f70 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -90,5 +90,83 @@ mod thread { *signaled = true; self.condvar.notify_one(); } + + fn check(&self) -> bool { + let signaled = self.mutex.lock().unwrap(); + *signaled + } + } + + /// Single-threaded std-based executor, that can be killed. + pub struct KillableExecutor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + signaler: &'static Signaler, + kill_switch: &'static Signaler, + } + + impl KillableExecutor { + /// Create a new Executor. + pub fn new() -> Self { + let signaler = Box::leak(Box::new(Signaler::new())); + let kill_switch = Box::leak(Box::new(Signaler::new())); + Self { + inner: raw::Executor::new(signaler as *mut Signaler as *mut ()), + not_send: PhantomData, + signaler, + kill_switch, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor, and a [`Killer`] that can be used to abort the executor. + /// Use [`Spawner`] to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function returns after the `Executor` is killed. + pub fn run(&'static mut self, init: impl FnOnce(Spawner, Killer)) { + let killer = Killer::new(self.kill_switch); + + init(self.inner.spawner(), killer); + + while !self.kill_switch.check() { + unsafe { self.inner.poll() }; + self.signaler.wait(); + } + } + } + + /// Handle to kill a `KillableExecutor`. + #[derive(Copy, Clone)] + pub struct Killer { + kill_switch: &'static Signaler, + not_send: PhantomData<*mut ()>, + } + + impl Killer { + fn new(kill_switch: &'static Signaler) -> Self { + Self { + kill_switch, + not_send: PhantomData, + } + } + + /// Kill the associated `KillableExecutor`. + pub fn kill(&self) { + self.kill_switch.signal(); + } } }