Skip to content
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

embassy-time: Timer with_cancellation #3906

Open
kelko opened this issue Feb 21, 2025 · 3 comments
Open

embassy-time: Timer with_cancellation #3906

kelko opened this issue Feb 21, 2025 · 3 comments

Comments

@kelko
Copy link

kelko commented Feb 21, 2025

TL;DR:

I want to able to configure a Timer::after(time).await call to return early once I need to (similar to .NET Task.Delay with additional cancellation token as parameter).

I can combine Timer::after already with with_timeout or with_deadline if I see this correctly. A with_cancellation would be nice as well for situations I don't know up front when I need to cancel a timer (or generally a Future), similar to the CancellationToken in .NET.

background

I have a task that waits a certain amount of time to set a flag (AtomicBool) used in other places to switch some mode. This way I switch through modes in an automatic fashion to control what I display.

static CANCEL_PAGE: AtomicBool = AtomicBool::new(false);

pub async fn rotate(&self, spawner: Spawner) -> ! {
  // ...
  loop {
    // state handling for the loops current iteration
    let duration = // calculate duration for next cycle
    spawner.must_spawn(next_pane_counter(duration));
    // main logic stuff. In some internal places I check CANCEL_PAGE and break to the main loop if necessary
  }
}

#[embassy_executor::task]
async fn next_pane_counter(time: Duration) {
    Timer::after(time).await;
    CANCEL_PAGE.store(true, Ordering::Release);
}

That works fine. Maybe not best way to solve it, but it works.

Only now I want to be able to react on external interrupts (like button presses). I made a different function which also can set CANCEL_PAGE, and that works so far. But once the main loop continues a new next_pane_counter is spawned - and will crash once the pool_size is overstepped. But I don't know at compile time what the pool size needs to be, as the interrupts can come quickly.

Cancelling the previously spawned task is requested (see #3197) but not yet possible.
Being able to return early from the timer inside next_pane_counter would allow me to handle that situation on my own. But I don't see any way to return early from the timer.

@swork
Copy link
Contributor

swork commented Feb 27, 2025

Could next_pane_counter take a embassy-sync's Channel::Receiver and select() on it and the timer? A message would serve as your timer-cancellation. Something like this:

static CANCEL_PAGE: AtomicBool = AtomicBool::new(false);

pub async fn rotate(&self, spawner: Spawner) -> ! {
  loop {
    let duration = whatever;
    let channel: Channel<NoopRawMutex, u8, 1> = Channel::new();
    spawner.must_spawn(next_pane_counter(duration, channel.receiver()));
    // at some point before the end of the loop:
    channel.send(b'X');
    // An await point is probably necessary to give next_pane_counter a chance to run.
  }
}

#[embassy_executor::task]
async fn next_pane_counter(time: Duration, rec: Receiver) {
    select(Timer::after(time), rec.receive()).await;
    CANCEL_PAGE.store(true, Ordering::Release);
}

@kelko
Copy link
Author

kelko commented Feb 27, 2025

Interesting. I wasn't aware of select.
I have to check that out. Thanks.

@kelko
Copy link
Author

kelko commented Mar 3, 2025

@swork Where does the select method in your example come from? Which crate?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants