embassy-rs/embassy · critical
Can only take the executor once
Error message
Can only take the executor once
What it means
This panic fires when `Executor::new()` is called more than once in a program. The executor is a process-wide singleton guarded by the EXECUTOR_ONCE atomic (compare_exchange UNINIT -> TAKEN); a second call fails the CAS and panics, because two executors would compete for the same thread pender and interrupt state. Rust panics abort/embedded programs cannot be caught, so this is a hard programming error.
Solutions
- Find and remove the duplicate `Executor::new()` call so exactly one exists per core/thread.
- Create the executor once in `main` and pass `&'static` references (via `Executor::new()` + `static` or `SingletonToken`-style sharing) to other modules instead of re-creating it.
- If two independent task domains are needed, use separate executor mechanisms designed for that (e.g. interrupt executor or a second core's executor), not two calls on the same core.
Example fix
// before
static EXEC: Executor = Executor::new();
fn setup() { let ex2 = Executor::new(); } // panics
// after
static EXEC: Executor = Executor::new();
fn setup() { /* use &EXEC, never call Executor::new() again */ } Defensive patterns
Strategy: validation
Validate before calling
// Call Executor::new() exactly once; keep it in a single static: static EXECUTOR: StaticCell<Executor> = StaticCell::new(); let executor: &'static mut Executor = EXECUTOR.init(Executor::new());
Prevention
- Never call Executor::new() outside of main/startup; search codebase for all call sites before adding one.
- Store the executor in a static and share &'static references instead of re-constructing.
- Keep executor creation in one dedicated init module or BSP function.
When it happens
Trigger: Calling `Executor::new()` twice, e.g. once in main and once in a helper/spawned task, or calling it again after an earlier init path (like a board-support init function) already created it.
Common situations: Copy-pasting executor setup from examples into a second module; refactoring startup code so two call sites both construct the executor; accidentally linking two crates that each create an embassy executor for the same core.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- DMA data error
- MCLK frequency < 9.5 MHz is not compatible with the TRNG
- Unknown value
- Invalid value
- ErevidIsZero
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/278e98de4ab4cdcf.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-mcxa/src/executor.rs:68
let context = context as usize;
// Try to make Rust optimize the branching away if we only use thread mode.
if context == THREAD_PENDER {
TASKS_PENDING.store(true, Ordering::Release);
cortex_m::asm::sev();
}
}
}
impl Executor {
// Note: We don't really want a Default impl for this singleton.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let res = EXECUTOR_ONCE.compare_exchange(EXECUTOR_UNINIT, EXECUTOR_TAKEN, Ordering::AcqRel, Ordering::Relaxed);
if res.is_err() {
panic!("Can only take the executor once");
}
Self {
inner: raw::Executor::new(THREAD_PENDER as *mut ()),
not_send: PhantomData,
}
}
/// Run the executor.
///
/// The `init` closure is called with a [`Spawner`] that spawns tasks on
/// this executor. Use it 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 theView on GitHub (pinned to 463a07b963)