Skip to content

Race condition(?) in interrupt which freezes whole program #3384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
bagbag opened this issue Apr 14, 2025 · 3 comments
Open

Race condition(?) in interrupt which freezes whole program #3384

bagbag opened this issue Apr 14, 2025 · 3 comments
Labels
bug Something isn't working
Milestone

Comments

@bagbag
Copy link

bagbag commented Apr 14, 2025

Bug description

I have the following code, which does:

Task1: Loop: Toggle GPIO3, wait 2500us, toggle, wait 9876us
Task2: Loop: Waits for edge on GPIO21 (connected to GPIO3), debounced with 2500us using select (not sure if that is relevant)

After a somewhat random amount of toggles, the whole thing freezes (apparently).
If I change one of the 2500us delays to for example 2480us, it happens with a lower probability per toggle.
If the delays are more than 50us apart, it doesnt happen at all (or I didn't wait long enough). So timing is key I guess.

If I start the debugger and press pause in this seemingly frozen state, I always see the following stack trace:

is_interrupt_set #[inline] (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-hal-1.0.0-beta.0/src/gpio/mod.rs:1771)
drop (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-hal-1.0.0-beta.0/src/gpio/mod.rs:2419)
drop_in_place<esp_hal::gpio::asynch::{impl#1}::wait_for_any_edge::{async_fn_env#0}> #[inline] (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-hal-1.0.0-beta.0/src/gpio/mod.rs:2361)
drop_in_place<embassy_futures::select::Select<esp_hal::gpio::asynch::{impl#1}::wait_for_any_edge::{async_fn_env#0}, embassy_time::timer::Timer>> #[inline] (/home/patrick/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:524)
{async_fn#0} #[inline] (/home/patrick/projects/esp-hang/src/bin/main.rs:75)
{async_fn#0} #[inline] (/home/patrick/projects/esp-hang/src/bin/main.rs:67)
poll<esp_hang::__stepper_decoder_task_task::{async_fn_env#0}> (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.7.0/src/raw/mod.rs:214)
{closure#0} #[inline] (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.7.0/src/raw/mod.rs:430)
dequeue_all<embassy_executor::raw::{impl#9}::poll::{closure_env#0}> #[inline] (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.7.0/src/raw/run_queue_atomics.rs:85)
poll #[inline] (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.7.0/src/raw/mod.rs:423)
poll (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-executor-0.7.0/src/raw/mod.rs:533)
handle_interrupt #[inline] (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-hal-1.0.0-beta.0/src/interrupt/riscv.rs:486)
handle_interrupts (/home/patrick/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/esp-hal-1.0.0-beta.0/src/interrupt/riscv.rs:465)
<unknown function @ 0x4080035a> (Unknown Source:0)

The following loop seems to never end:

while self.pin.is_interrupt_set() {}

To Reproduce

#![no_std]
#![no_main]
#![feature(impl_trait_in_assoc_type)]

use defmt as _;
use embassy_futures::select::{Either, select};
use esp_hal_embassy::InterruptExecutor;
use panic_rtt_target as _;

use defmt::info;
use embassy_executor::Spawner;
use embassy_time::Timer;
use esp_hal::{
    clock::CpuClock,
    gpio::{DriveMode, GpioPin, Input, InputConfig, Level, Output, OutputConfig, Pull},
    interrupt::{Priority, software::SoftwareInterruptControl},
    timer::systimer::SystemTimer,
};
use static_cell::StaticCell;

static INTERRUPT_EXECUTOR: StaticCell<InterruptExecutor<0>> = StaticCell::new();

#[esp_hal_embassy::main]
async fn main(spawner: Spawner) {
    rtt_target::rtt_init_defmt!();

    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let p = esp_hal::init(config);

    let system_timer = SystemTimer::new(p.SYSTIMER);
    esp_hal_embassy::init(system_timer.alarm0);

    info!("Initialized!");

    let software_interrupts = SoftwareInterruptControl::new(p.SW_INTERRUPT);
    let interrupt_exeuctor = INTERRUPT_EXECUTOR.init_with(|| InterruptExecutor::new(software_interrupts.software_interrupt0));
    let interrupt_spwaner = interrupt_exeuctor.start(Priority::Priority1);

    interrupt_spwaner.must_spawn(stepper_decoder_task(p.GPIO21));
    interrupt_spwaner.must_spawn(toggle_task(p.GPIO3));
}

#[embassy_executor::task]
pub async fn toggle_task(pin: GpioPin<3>) {
    let output_config = OutputConfig::default().with_drive_mode(DriveMode::PushPull);
    let mut output = Output::new(pin, Level::Low, output_config);

    loop {
        output.toggle();
        Timer::after_micros(2500).await;

        // the following is just to have a visible indicator if it is still running or not
        output.toggle();
        Timer::after_micros(9876).await; // this timeout doesn't seem to change anything about that at all
    }
}

#[embassy_executor::task]
pub async fn stepper_decoder_task(pin: GpioPin<21>) {
    let input_config = InputConfig::default().with_pull(Pull::Down);
    let mut input = Input::new(pin, input_config);

    loop {
        wait_for_input_changes_debounced(&mut input, 2500).await;
    }
}

async fn wait_for_input_changes_debounced(input: &mut Input<'_>, microseconds: u64) {
    input.wait_for_any_edge().await;

    loop {
        let res = select(input.wait_for_any_edge(), Timer::after_micros(microseconds)).await;

        if let Either::Second(_) = res {
            break;
        }
    }
}

Expected behavior

Keep running. I don't think the code creates any race condition in itself

Environment

  • Target device: ESP32-C6
  • Crate name and version: esp-hal-1.0.0-beta.0
@bagbag bagbag added bug Something isn't working status:needs-attention This should be prioritized labels Apr 14, 2025
@bugadani
Copy link
Contributor

Use Io::set_interrupt_priority to raise the GPIO interrupt priority.

@bagbag
Copy link
Author

bagbag commented Apr 14, 2025

Thanks!

Setting the priority higher than the executor works. Does this mean this behavior is correct? Or is this just a workaround?

@bugadani
Copy link
Contributor

it's complicated

@MabezDev MabezDev removed the status:needs-attention This should be prioritized label Apr 16, 2025
@MabezDev MabezDev added this to the 1.0.0-beta.1 milestone Apr 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
Status: Todo
Development

No branches or pull requests

3 participants