embassy-rs/embassy · error

Task is already in use

Error message

Task is already in use

What it means

new_many_to_many panics when a task's SUBSCRIBE register already holds a non-zero value, meaning another PPI/DPPI channel is already subscribed to that task. This guard prevents silently reconfiguring hardware task routing. The driver checks the register with a volatile read before writing the new channel configuration.

Solutions

  1. Drop or tear down the existing PPI channel that subscribes to the same task before calling new_many_to_many again.
  2. Audit the task list for duplicates — ensure no task appears in two configurations at once.
  3. If reconfiguring is intentional, disable the old DPPI channel and zero its SUBSCRIBE registers first.
  4. Restructure the application so one channel handles all events->task combinations for a shared task.

Example fix

// before
let c1 = ManyToMany::new_many_to_many(p.PPI_CH0, [timer_event], [timer_task_clear]);
let c2 = ManyToMany::new_many_to_many(p.PPI_CH1, [other_event], [timer_task_clear]); // panics
// after
let c1 = ManyToMany::new_many_to_many(p.PPI_CH0, [timer_event, other_event], [timer_task_clear]);
drop(c1); // if a rebuild is truly needed, drop before recreating
Defensive patterns

Strategy: validation

Validate before calling

// Track claimed tasks in application state; check before configuring.
let task_reg = task.subscribe_reg().read_volatile();
if task_reg != 0 {
    // task already subscribed: reuse existing channel or tear it down first
}

Prevention

When it happens

Trigger: Calling dppi::ManyToMany::new_many_to_many(ch, events, tasks) with a Task whose SUBSCRIBE register is non-zero — i.e. the task was already claimed by a previously configured PPI channel (via new_one_to_one, new_many_to_many, or manual register writes) that is still enabled or not torn down.

Common situations: Configuring two PPI channels that both drive the same timer task (e.g. two CLEAR/START task connections); calling new_many_to_many twice with an overlapping task; dropping a previous channel struct without disabling the DPPI channel so registers stay non-zero; reconfiguring after a soft reset without clearing SUBSCRIBE registers.

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/0930596e6bbb75fa. Report an issue: GitHub.

Appendix: source

Thrown at embassy-nrf/src/ppi/dppi.rs:34

    }
}

impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> {
    /// Configure PPI channel to trigger both `task1` and `task2` on `event`.
    pub fn new_one_to_two(ch: Peri<'d, C>, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self {
        Ppi::new_many_to_many(ch, [event], [task1, task2])
    }
}

impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usize>
    Ppi<'d, C, EVENT_COUNT, TASK_COUNT>
{
    /// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires.
    pub fn new_many_to_many(ch: Peri<'d, C>, events: [Event<'d>; EVENT_COUNT], tasks: [Task<'d>; TASK_COUNT]) -> Self {
        let val = DPPI_ENABLE_BIT | (ch.number() as u32 & DPPI_CHANNEL_MASK);
        for task in tasks {
            if unsafe { task.subscribe_reg().read_volatile() } != 0 {
                panic!("Task is already in use");
            }
            unsafe { task.subscribe_reg().write_volatile(val) }
        }
        for event in events {
            if unsafe { event.publish_reg().read_volatile() } != 0 {
                panic!("Event is already in use");
            }
            unsafe { event.publish_reg().write_volatile(val) }
        }

        Self { ch, events, tasks }
    }
}

impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, C, EVENT_COUNT, TASK_COUNT> {
    /// Enables the channel.
    pub fn enable(&mut self) {
        let n = self.ch.number();

View on GitHub (pinned to 463a07b963)