Skip to content

Conversation

aurelj
Copy link
Contributor

@aurelj aurelj commented Oct 3, 2025

This fixes an issue when using never ending timers such as: Timer::at(Instant::MAX)

When such a timer is canceled and a another timer is initiated in the same task,
the new timer won't actually be added to the queue and thus will never wake.

The issue is that when the never ending timer is added to the queue, next_expiration()
is called and will simply remove the timer from the queue (it will never be reached
so there is no point in keeping it in the queue and checking it every time), but it
will not reset the waker to None.
So when the next timer is scheduled with schedule_wake(), a waker will already be
present and schedule_wake() will consider this item is already part of the queue
and won't add it in the queue anymore, so it will also never wake !

Here is an example the exhibit this issue. First time the select() will be called with
a never ending timer, but after the first edge change on input, select() is
called with a 500ms timer that should trigger a break of the loop if no edge happens
during the 500ms.
Currently, this loop will never end. The PR fixes this issue.

#[embassy_executor::task]
async fn stable_input(mut input: ExtiInput<'static>) {
    let mut deadline = None;

    loop {
        match select(
            input.wait_for_any_edge(),
            Timer::at(deadline.unwrap_or(Instant::MAX)),
        )
        .await
        {
            Either::First(()) => {
                deadline = Some(Instant::now() + Duration::from_millis(500));
            }
            Either::Second(_) => break,
        }
    }

    info!("stable input");
}

For the record, this bug was introduced in 74037f0

@aurelj aurelj force-pushed the never_ending_timer branch from dbfb9e6 to bc79584 Compare October 3, 2025 18:35
item.next.set(None);
// Ensure there is no waker left in items that are not part of the timer queue
// even when the waker was not called (for timer that never expires, ie expires_at = u64::MAX)
item.waker = None;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment isn't very clear, the problem was schedule_wake uses the presence of a waker to determine if an item is part of the queue or not, and that invariant wasn't maintained.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, reading this comment again now, I realize it wasn't very clear.
Updated it now. It is hopefully clearer.

@aurelj aurelj force-pushed the never_ending_timer branch from bc79584 to cb61089 Compare October 14, 2025 20:27
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

Successfully merging this pull request may close these issues.

2 participants