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

  1. Find and remove the duplicate `Executor::new()` call so exactly one exists per core/thread.
  2. 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.
  3. 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

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


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 the

View on GitHub (pinned to 463a07b963)