embassy-rs/embassy · error

InterruptExecutor::spawner() called on uninitialized…

Error message

InterruptExecutor::spawner() called on uninitialized executor.

What it means

`InterruptExecutor::spawner` reads the executor's inner state which is only initialized by `start`; if `start` was never called the MaybeUninit is still uninitialized and dereferencing would be UB, so the driver checks the `started` flag and panics instead.

Solutions

  1. Ensure `InterruptExecutor::start(irq)` is called before any `spawner()` call, ideally as the first step in main/boot
  2. Obtain the `SendSpawner` once after start and pass it down instead of calling `spawner()` ad hoc
  3. If start order is nondeterministic, restructure so spawner acquisition happens in the same function/after start via a channel or OnceCell

Example fix

// before
let spawner = EXECUTOR.spawner(); // start not yet called
EXECUTOR.start(irqs::Irq0);
// after
EXECUTOR.start(irqs::Irq0);
let spawner = EXECUTOR.spawner();
Defensive patterns

Strategy: validation

Validate before calling

fn get_spawner_after_start(irq: impl InterruptNumber) -> embassy_executor::SendSpawner {
    EXECUTOR.start(irq); // if start may double-run, guard it first
    EXECUTOR.spawner()
}

Prevention

When it happens

Trigger: Calling `EXECUTOR.spawner()` before `EXECUTOR.start(irq)` anywhere in the program, e.g. spawning tasks in a task setup function that runs before the main init sequence.

Common situations: Reordering init code so a module's `statics`/task spawner executes before executor start; a spawn path in a library that assumes the executor is already running; race between two init paths where spawner is fetched first.

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


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/a16c5e53d87ef05e. Report an issue: GitHub.

Appendix: source

Thrown at embassy-rp/src/executor.rs:256

            }

            let executor = unsafe { (&*self.executor.get()).assume_init_ref() };

            unsafe { NVIC::unmask(irq) }

            executor.spawner().make_send()
        }

        /// Get a SendSpawner for this executor
        ///
        /// This returns a [`SendSpawner`](embassy_executor::SendSpawner) you can use to spawn tasks on this
        /// executor.
        ///
        /// This MUST only be called on an executor that has already been started.
        /// The function will panic otherwise.
        pub fn spawner(&'static self) -> embassy_executor::SendSpawner {
            if !critical_section::with(|cs| self.started.borrow(cs).get()) {
                panic!("InterruptExecutor::spawner() called on uninitialized executor.");
            }
            let executor = unsafe { (&*self.executor.get()).assume_init_ref() };
            executor.spawner().make_send()
        }
    }
}

View on GitHub (pinned to 463a07b963)