Skip to content

Commit fde725c

Browse files
committed
[timers] Fix missing wakeups in timer service
Exactly as (#5085) but for the timer service. Changes to the scheduling were not correctly waking up the wakers for `poll_next_timer`. This was ok in the existing code because we were reconstructing the timer stream in every `LeaderState::run` call. Now that this is changing in the next PR, this has to be fixed. > For full disclaimer, this was caught and fixed by codex.
1 parent 010ec45 commit fde725c

2 files changed

Lines changed: 87 additions & 13 deletions

File tree

crates/timer/src/service/mod.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@
1010

1111
#![allow(clippy::enum_variant_names)]
1212

13-
use pin_project::pin_project;
14-
use restate_types::timer::TimerKey;
1513
use std::collections::HashSet;
1614
use std::fmt::Debug;
1715
use std::future;
1816
use std::future::Future;
1917
use std::pin::Pin;
2018
use std::task::{Context, Poll, Waker, ready};
19+
20+
use pin_project::pin_project;
2121
use tokio_util::sync::ReusableBoxFuture;
2222
use tracing::trace;
2323

24+
use restate_types::timer::TimerKey;
25+
2426
pub mod clock;
2527
#[cfg(test)]
2628
mod tests;
@@ -56,6 +58,7 @@ enum ProcessTimersState<TimerKey, SleepFuture> {
5658
ReadNextTimer,
5759
AwaitTimer {
5860
timer_key: TimerKey,
61+
waker: Waker,
5962
#[pin]
6063
sleep: SleepFuture,
6164
},
@@ -234,12 +237,15 @@ where
234237
ProcessTimersStateProj::ReadNextTimer => {
235238
// nothing to do because peek timer will be read next
236239
}
237-
ProcessTimersStateProj::AwaitTimer { timer_key, .. } => {
240+
ProcessTimersStateProj::AwaitTimer {
241+
timer_key, waker, ..
242+
} => {
238243
// we might wait for a later timer if the newly added timer fires earlier
239244
if new_timer_key < *timer_key {
240245
trace!(
241246
"Reset process timer state to ReadNextTimer because added timer fires earlier."
242247
);
248+
waker.wake_by_ref();
243249
process_timers_state.set(ProcessTimersState::ReadNextTimer);
244250
}
245251
}
@@ -300,8 +306,11 @@ where
300306
ProcessTimersStateProj::ReadNextTimer => {
301307
// nothing to do
302308
}
303-
ProcessTimersStateProj::AwaitTimer { timer_key, .. } => {
309+
ProcessTimersStateProj::AwaitTimer {
310+
timer_key, waker, ..
311+
} => {
304312
if key == *timer_key {
313+
waker.wake_by_ref();
305314
process_timers_state.set(ProcessTimersState::ReadNextTimer);
306315

307316
trace!("Skip awaiting removed timer '{key:?}'. Read next timer.");
@@ -344,7 +353,8 @@ where
344353

345354
loop {
346355
match state.as_mut().project() {
347-
StateProj::Idle(_) => {
356+
StateProj::Idle(waker) => {
357+
waker.clone_from(cx.waker());
348358
return Poll::Pending;
349359
}
350360
StateProj::LoadTimers { removed_timers } => {
@@ -423,6 +433,7 @@ where
423433
);
424434
process_timers_state.set(ProcessTimersState::AwaitTimer {
425435
timer_key: timer_key.clone(),
436+
waker: cx.waker().clone(),
426437
sleep,
427438
});
428439
} else {
@@ -455,7 +466,8 @@ where
455466
state.set(State::LoadTimers { removed_timers });
456467
}
457468
}
458-
ProcessTimersStateProj::AwaitTimer { sleep, .. } => {
469+
ProcessTimersStateProj::AwaitTimer { sleep, waker, .. } => {
470+
waker.clone_from(cx.waker());
459471
ready!(sleep.poll(cx));
460472
process_timers_state.set(ProcessTimersState::TriggerTimer);
461473
}

crates/timer/src/service/tests.rs

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,27 @@
88
// the Business Source License, use of this software will be governed
99
// by the Apache License, Version 2.0.
1010

11-
use crate::service::clock::TokioClock;
12-
use crate::service::clock::tests::ManualClock;
13-
use crate::{Timer, TimerReader, TimerService};
14-
use futures_util::FutureExt;
15-
use restate_test_util::let_assert;
16-
use restate_types::time::MillisSinceEpoch;
17-
use restate_types::timer::TimerKey;
1811
use std::cmp::Ordering;
1912
use std::collections::BTreeMap;
2013
use std::fmt::Debug;
2114
use std::pin::Pin;
15+
use std::sync::atomic::{AtomicBool, Ordering as AtomicOrdering};
2216
use std::sync::{Arc, Mutex};
17+
use std::task::{Context, Poll, Wake, Waker};
2318
use std::time::{Duration, SystemTime};
19+
20+
use futures_util::FutureExt;
2421
use test_log::test;
2522
use tokio::sync::oneshot;
2623

24+
use restate_test_util::let_assert;
25+
use restate_types::time::MillisSinceEpoch;
26+
use restate_types::timer::TimerKey;
27+
28+
use crate::service::clock::TokioClock;
29+
use crate::service::clock::tests::ManualClock;
30+
use crate::{Timer, TimerReader, TimerService};
31+
2732
#[derive(Debug, Clone)]
2833
struct MockTimerReader<T>
2934
where
@@ -159,6 +164,14 @@ impl TimerKey for TimerValue {
159164
}
160165
}
161166

167+
struct TestWaker(AtomicBool);
168+
169+
impl Wake for TestWaker {
170+
fn wake(self: Arc<Self>) {
171+
self.0.store(true, AtomicOrdering::Relaxed);
172+
}
173+
}
174+
162175
#[test(tokio::test)]
163176
async fn no_timer_is_dropped() {
164177
let timer_reader = MockTimerReader::new();
@@ -324,6 +337,55 @@ async fn earlier_timers_replace_older_ones() {
324337
}
325338
}
326339

340+
#[test]
341+
fn adding_earlier_timer_wakes_service() {
342+
let clock = ManualClock::new(MillisSinceEpoch::UNIX_EPOCH);
343+
let timer_reader = MockTimerReader::<TimerValue>::new();
344+
timer_reader.add_timer(TimerValue::new(1, 10.into()));
345+
346+
let service = TimerService::new(clock, Some(1), timer_reader);
347+
tokio::pin!(service);
348+
349+
let test_waker = Arc::new(TestWaker(AtomicBool::new(false)));
350+
let waker = Waker::from(Arc::clone(&test_waker));
351+
let mut cx = Context::from_waker(&waker);
352+
353+
assert_eq!(service.as_mut().poll_next_timer(&mut cx), Poll::Pending);
354+
355+
service
356+
.as_mut()
357+
.add_timer(TimerValue::new(0, MillisSinceEpoch::UNIX_EPOCH));
358+
359+
assert!(test_waker.0.load(AtomicOrdering::Relaxed));
360+
assert_eq!(
361+
service.as_mut().poll_next_timer(&mut cx),
362+
Poll::Ready(TimerValue::new(0, MillisSinceEpoch::UNIX_EPOCH))
363+
);
364+
}
365+
366+
#[test]
367+
fn removing_awaited_timer_wakes_service() {
368+
let clock = ManualClock::new(MillisSinceEpoch::UNIX_EPOCH);
369+
let timer_reader = MockTimerReader::<TimerValue>::new();
370+
let timer = TimerValue::new(0, 10.into());
371+
timer_reader.add_timer(timer);
372+
373+
let service = TimerService::new(clock, Some(1), timer_reader.clone());
374+
tokio::pin!(service);
375+
376+
let test_waker = Arc::new(TestWaker(AtomicBool::new(false)));
377+
let waker = Waker::from(Arc::clone(&test_waker));
378+
let mut cx = Context::from_waker(&waker);
379+
380+
assert_eq!(service.as_mut().poll_next_timer(&mut cx), Poll::Pending);
381+
382+
timer_reader.remove_timer(timer);
383+
service.as_mut().remove_timer(timer);
384+
385+
assert!(test_waker.0.load(AtomicOrdering::Relaxed));
386+
assert_eq!(service.as_mut().poll_next_timer(&mut cx), Poll::Pending);
387+
}
388+
327389
async fn yield_to_timer_service<
328390
Timer: crate::Timer + Debug + 'static,
329391
Clock: crate::Clock,

0 commit comments

Comments
 (0)