embassy-rs/embassy · error

the RNG is in use

Error message

the RNG is in use

What it means

The embassy RNG global driver is guarded by a `Mutex` over a single `Option<Rng<'static, Blocking>>`. `with_rng` uses `try_lock` and panics with "the RNG is in use" if the lock is already held — meaning two tasks (or an interrupt context) are concurrently using the RNG. Embassy mutexes are not reentrant, so nested/parallel access is a hard error.

Solutions

  1. Serialize RNG access yourself: perform random reads from a single task, or use a channel/actor that owns randomness
  2. Retry or defer one consumer — since it's `try_lock`, moving the second call to another tick avoids contention
  3. Check for accidental nesting: don't call another RNG-based API while holding a random-generating call (e.g. generating a UUID inside `fill_bytes` handling)
  4. Use an embassy `signal`/`mutex` wrapper so callers await instead of panicking

Example fix

// before
let a = rand_bytes().await;   // task A
let b = rand_bytes().await;   // task B, same tick -> try_lock panic
// after
static RNG_SEM: Semaphore<1> = Semaphore::new(1);
let permit = RNG_SEM.acquire().await;
let a = rand_bytes().await;
drop(permit); // then task B proceeds safely
Defensive patterns

Strategy: validation

Validate before calling

// Ensure only one task generates randomness at a time
static RNG_GATE: Mutex<CriticalSectionRawMutex, ()> = Mutex::new(());
let _g = RNG_GATE.lock().await; // serialize callers instead of try_lock panicking

Try / catch

// Embassy panics aren't catchable; avoid contention instead:
match DRIVER.try_lock() {
    Ok(_) => { /* safe to generate randomness */ }
    Err(_) => { /* defer to next tick or return Pending */ }
}

Prevention

When it happens

Trigger: Two async tasks concurrently calling RNG-backed APIs (e.g. `fill_bytes` via `Rng`/random helpers); calling an RNG function from inside another RNG callback (re-entrant nesting) so the mutex is locked twice in the same execution context.

Common situations: Multiple subsystems (TLS, device ID generation, UUID generation) sampling randomness simultaneously; spawning two tasks that both draw entropy at startup; blocking RNG call inside an async context that also uses the RNG from an executor thread.

Related errors


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

Appendix: source

Thrown at embassy-stm32/src/rng.rs:678

pub(crate) mod driver {
    use embassy_crypto::Error as CryptoError;
    use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
    use embassy_sync::mutex::Mutex;

    use super::{MAX_RESET_RETRIES, Rng};
    use crate::mode::Blocking;

    foreach_peripheral!(
        (rng, $inst:ident) => {
            type Instance = crate::peripherals::$inst;
        };
    );

    static DRIVER: Mutex<CriticalSectionRawMutex, Option<Rng<'static, Blocking>>> = Mutex::new(None);

    /// Runs `f` on the RNG, starting it first if needed.
    pub(crate) fn with_rng<R>(f: impl FnOnce(&mut Rng<'static, Blocking>) -> R) -> R {
        let mut driver = DRIVER.try_lock().expect("the RNG is in use");
        let rng = driver.get_or_insert_with(|| {
            let peri = unsafe { Instance::steal() };
            #[cfg(rng_v1)]
            {
                Rng::new_inner(peri)
            }
            #[cfg(not(rng_v1))]
            {
                Rng::new_inner(peri, super::RngConfig::default())
            }
        });
        f(rng)
    }

    /// Starts the RNG if it is not running yet.
    pub(crate) fn ensure_running() {
        with_rng(|_| ());
    }

View on GitHub (pinned to 463a07b963)