embassy-rs/embassy · error

Blocking at SleepLevel

Error message

Blocking at SleepLevel {:?} would overflow

What it means

Each SleepLevel has a saturating u8 counter (SLEEP_BLOCKS) of active WakeGuards; block() detects that another increment would hit u8::MAX and overflow the count, which would corrupt the sleep-blocking bookkeeping. This is a driver-internal guard against ~255 outstanding WakeGuards at one level, effectively indicating a WakeGuard leak.

Solutions

  1. Drop WakeGuards when done rather than keeping them alive; each guard increments the counter and its Drop decrements it.
  2. Audit code that std::mem::forget's or leaks a WakeGuard.
  3. If >255 simultaneously-live guards at one sleep level is legitimate, raise the counter type from u8.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at embassy-mspm0/src/low_power/mod.rs:85 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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

Appendix: source

Thrown at embassy-mspm0/src/low_power/mod.rs:85

    mspm0g151x,
    mspm0g351x,
    mspm0g518x,
    mspm0c110x,
    mspm0c1105_c1106,
    mspm0h321x
)))]
compile_error!("the `low-power` feature is not implemented for this chip family");

pub use crate::sysctl::SleepLevel;

static SLEEP_BLOCKS: [AtomicU8; 5] = [const { AtomicU8::new(0) }; 5];

/// Block sleep at `level` and every deeper mode. Paired with [`unblock`] by
/// [`WakeGuard`](crate::sysctl::WakeGuard).
pub(crate) fn block(level: SleepLevel) {
    trace!("Blocking sleep at level {:?}", level);
    if SLEEP_BLOCKS[level as usize].fetch_add(1, Ordering::Relaxed) == (u8::MAX - 1) {
        panic!("Blocking at SleepLevel {:?} would overflow", level)
    };
}

/// Remove a block previously added at `level`.
pub(crate) fn unblock(level: SleepLevel) {
    trace!("Unblocking sleep at level {:?}", level);
    SLEEP_BLOCKS[level as usize].fetch_sub(1, Ordering::Relaxed);
}

/// Deepest mode currently permitted, or `None` if all deep sleep is blocked.
fn deepest_allowed() -> Option<SleepLevel> {
    for (i, blocks) in SLEEP_BLOCKS.iter().enumerate() {
        if blocks.load(Ordering::Relaxed) > 0 {
            return i.checked_sub(1).map(|j| SleepLevel::LEVELS[j]);
        }
    }
    Some(SleepLevel::Standby1)
}

View on GitHub (pinned to 463a07b963)