From 2119c999b6602bbc2926d25c0325a4f66c557e7d Mon Sep 17 00:00:00 2001 From: Sean Michael Wykes <8363933+cryptographix@users.noreply.github.com> Date: Wed, 19 Nov 2025 21:21:51 -0300 Subject: [PATCH 1/5] Added KillableExecutor --- embassy-executor/src/arch/std.rs | 75 +++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index c62ab723b0..10907cb3c3 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -91,4 +91,77 @@ mod thread { self.condvar.notify_one(); } } -} + + + /// 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) + /// + /// + 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(); + } + } + } + + #[derive(Copy, Clone)] + pub struct Killer { + kill_switch: &'static Signaler, + not_send: PhantomData<*mut ()>, + } + + impl Killer { + pub fn new(kill_switch: &'static Signaler) -> Self { + Self { + kill_switch, + not_send: PhantomData, + } + } + + pub fn kill(&self) { + self.kill_switch.signal(); + } + } + + } From 80a372d9701a8b09f02d4cff162205d538e18bb7 Mon Sep 17 00:00:00 2001 From: Sean Michael Wykes <8363933+cryptographix@users.noreply.github.com> Date: Wed, 19 Nov 2025 21:53:29 -0300 Subject: [PATCH 2/5] Added changelog entry for killable std executor --- embassy-executor/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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 From 18bd430fe2624366b013b11f647a845211f92e89 Mon Sep 17 00:00:00 2001 From: Sean Michael Wykes <8363933+cryptographix@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:06:28 -0300 Subject: [PATCH 3/5] fmt --- embassy-executor/src/arch/std.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 10907cb3c3..5e63acd815 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -92,7 +92,6 @@ mod thread { } } - /// Single-threaded std-based executor, that can be killed. pub struct KillableExecutor { inner: raw::Executor, @@ -163,5 +162,4 @@ mod thread { self.kill_switch.signal(); } } - - } +} From 8073f299ad82c01c6cf5a4f0d32f6ba23a7cbc29 Mon Sep 17 00:00:00 2001 From: Sean Michael Wykes <8363933+cryptographix@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:11:21 -0300 Subject: [PATCH 4/5] fix botched cleanup --- embassy-executor/src/arch/std.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 5e63acd815..2ead8fb04c 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -90,6 +90,11 @@ 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. From 70b0f934bdbdcbaed615d5eb5214bc1c3316943b Mon Sep 17 00:00:00 2001 From: Sean Michael Wykes <8363933+cryptographix@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:16:15 -0300 Subject: [PATCH 5/5] add docs --- embassy-executor/src/arch/std.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 2ead8fb04c..1083053f70 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -136,7 +136,7 @@ mod thread { /// - 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); @@ -149,6 +149,7 @@ mod thread { } } + /// Handle to kill a `KillableExecutor`. #[derive(Copy, Clone)] pub struct Killer { kill_switch: &'static Signaler, @@ -156,13 +157,14 @@ mod thread { } impl Killer { - pub fn new(kill_switch: &'static Signaler) -> Self { + 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(); }