Skip to content

Conversation

@ADD-SP
Copy link
Member

@ADD-SP ADD-SP commented Jun 3, 2025

Motivation

close #7380

Solution

The solution is straightforward.

Note that Instant::far_future() also acquires the mutex internally, so we have to fetch it before the self.inner.lock().

@Darksonn Darksonn added A-tokio Area: The main tokio crate M-time Module: tokio/time labels Jun 3, 2025
Comment on lines -294 to +300
inner.base += duration;
inner.base = inner
.base
.checked_add(duration)
.unwrap_or(far_future);
Copy link
Contributor

Choose a reason for hiding this comment

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

This avoids the panic, but I wonder how correct this is. Does the timer still behave correctly after this call?

Copy link
Member Author

Choose a reason for hiding this comment

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

while let Some(entry) = lock.wheel.poll(now) {
debug_assert!(unsafe { entry.is_pending() });
// SAFETY: We hold the driver lock, and just removed the entry from any linked lists.
if let Some(waker) = unsafe { entry.fire(Ok(())) } {
waker_list.push(waker);
if !waker_list.can_push() {
// Wake a batch of wakers. To avoid deadlock, we must do this with the lock temporarily dropped.
drop(lock);
waker_list.wake_all();
lock = self.inner.lock();
}
}
}

pub(crate) fn poll(&mut self, now: u64) -> Option<TimerHandle> {
loop {
if let Some(handle) = self.pending.pop_back() {
return Some(handle);
}
match self.next_expiration() {
Some(ref expiration) if expiration.deadline <= now => {
self.process_expiration(expiration);
self.set_elapsed(expiration.deadline);
}
_ => {
// in this case the poll did not indicate an expiration
// _and_ we were not able to find a next expiration in
// the current list of timers. advance to the poll's
// current time and do nothing else.
self.set_elapsed(now);
break;
}
}
}
self.pending.pop_back()
}

Good question, I read the code and I realized that the time driver keeps polling the wheel until wheel.elapsed == now.

So I think that even though the wheel rotates a lot of full revolution because of far_future, it still works correctly.

I also added more tests to ensure the correct behavior after a huge advance.

Comment on lines +288 to +290
// Retrieve `far_future` before acquiring the mutex to prevent deadlock,
// as `Instant::far_future()` also acquires a mutex internally.
let far_future = Instant::far_future().into_std();
Copy link
Contributor

Choose a reason for hiding this comment

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

We would ideally like to avoid acquiring the mutex twice every time just for supporting this one case.

This comment was marked as duplicate.

Copy link
Member Author

@ADD-SP ADD-SP Jun 6, 2025

Choose a reason for hiding this comment

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

inner.base = inner
    .base
    .checked_add(duration)
    .unwrap_or_else(|| inner.base + <30 years>);

To bypass the Mutex manually add 30 years on overflow, but this still panics after many times of time::advance(Duration::MAX), I don't have perfect idea yet.

Let me ask the issue author about the real (original) requirement, maybe there will be a better solution.

@ADD-SP
Copy link
Member Author

ADD-SP commented Jun 18, 2025

Superseded by #7394

@ADD-SP ADD-SP closed this Jun 18, 2025
@ADD-SP ADD-SP deleted the add_sp/time-fix-overflow branch June 18, 2025 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-tokio Area: The main tokio crate M-time Module: tokio/time

Projects

None yet

Development

Successfully merging this pull request may close these issues.

time::advance panics on Duration::MAX

2 participants