embassy-rs/embassy · error
InterruptExecutor::start() called multiple times on the…
Error message
InterruptExecutor::start() called multiple times on the same executor.
What it means
`InterruptExecutor::start` uses a `started` Cell guarded by a critical section to guarantee single initialization; calling `start` a second time on the same static executor instance panics because the executor's inner state is already initialized and re-init would corrupt it.
Solutions
- Call `start` exactly once per executor instance, e.g. in a dedicated `init()` guarded by `OnceLock`/`once` guard
- Use distinct static executors if you genuinely need to start executors at different times or priorities
- Guard the start call: check `started` first via `spawner()` succeeding, or wrap start in a `critical_section` check
- For multi-core, give each core its own InterruptExecutor static
Example fix
// before
fn init() { EXECUTOR.start(irqs::Irq0); }
init(); init(); // second call panics
// after
use core::sync::atomic::{AtomicBool, Ordering};
static INITED: AtomicBool = AtomicBool::new(false);
fn init() {
if INITED.swap(true, Ordering::SeqCst) { return; }
EXECUTOR.start(irqs::Irq0);
}
Defensive patterns
Strategy: validation
Validate before calling
use core::sync::atomic::{AtomicBool, Ordering};
static EXEC_STARTED: AtomicBool = AtomicBool::new(false);
fn start_executor_once(irq: impl InterruptNumber) -> Option<embassy_executor::SendSpawner> {
if EXEC_STARTED.swap(true, Ordering::SeqCst) { return None; }
Some(EXECUTOR.start(irq))
}
Prevention
- Call start exactly once from a single init function
- Use an atomic/OnceCell guard around start in reusable init code
- Give each core its own executor static in multi-core apps
When it happens
Trigger: Calling `executor.start(irq)` twice on the same `static EXECUTOR: InterruptExecutor`, e.g. in both an init function and a later restart path; running the same init code on both cores against a shared static executor; re-running init after a soft reset without re-creating the executor.
Common situations: Refactored boot code that invokes the executor setup from two places; a multi-core app where each core calls the same start function; tests running setup twice in one process with a `#[static_executor]`-style static.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- InterruptExecutor::spawner() called on uninitialized…
- Can only take the executor once
- timer must be stopped before setting counter
- Failed to configure PLL_SYS
- Failed to configure PLL_USB
AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10).
Data as JSON: /api/errors/6bec276ef10756ae.
Report an issue: GitHub.
Appendix: source
Thrown at embassy-rp/src/executor.rs:230
/// different "thread" (the interrupt), so spawning tasks on it is effectively
/// sending them.
///
/// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from
/// a task running in it.
///
/// # Interrupt requirements
///
/// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt).
///
/// This method already enables (unmasks) the interrupt, you must NOT do it yourself.
///
/// You must set the interrupt priority before calling this method. You MUST NOT
/// do it after.
///
/// [`SendSpawner`]: embassy_executor::SendSpawner
pub fn start(&'static self, irq: impl InterruptNumber) -> embassy_executor::SendSpawner {
if critical_section::with(|cs| self.started.borrow(cs).replace(true)) {
panic!("InterruptExecutor::start() called multiple times on the same executor.");
}
unsafe {
let context = (irq.number() as usize | (current_core() as usize) << 16) as *mut ();
(&mut *self.executor.get())
.as_mut_ptr()
.write(raw::Executor::new(context))
}
let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
unsafe { NVIC::unmask(irq) }
executor.spawner().make_send()
}
/// Get a SendSpawner for this executor
///View on GitHub (pinned to 463a07b963)