Skip to content

Commit 0a1e48f

Browse files
committed
Await Future::poll Completed before unsetting notify-required
When we mark a future as complete, if the user is using the `std::future::Future` impl to get notified, we shouldn't just assume we have completed the `Future` when we call the `Waker`. A `Future` may have been `drop`'d at that point (or may not be `poll`'d again) even though we wake the `Waker`. Because we now have a `callbacks_made` flag, we can fix this rather trivially, simply not setting the flag until the `Future` is `poll`'d `Complete`.
1 parent 5f053e4 commit 0a1e48f

File tree

1 file changed

+41
-5
lines changed

1 file changed

+41
-5
lines changed

lightning/src/util/wakers.rs

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,19 @@ impl<F: Fn() + Send> FutureCallback for F {
152152
}
153153

154154
pub(crate) struct FutureState {
155-
callbacks: Vec<Box<dyn FutureCallback>>,
155+
// When we're tracking whether a callback counts as having woken the user's code, we check the
156+
// first bool - set to false if we're just calling a Waker, and true if we're calling an actual
157+
// user-provided function.
158+
callbacks: Vec<(bool, Box<dyn FutureCallback>)>,
156159
complete: bool,
157160
callbacks_made: bool,
158161
}
159162

160163
impl FutureState {
161164
fn complete(&mut self) {
162-
for callback in self.callbacks.drain(..) {
165+
for (counts_as_call, callback) in self.callbacks.drain(..) {
163166
callback.call();
164-
self.callbacks_made = true;
167+
self.callbacks_made |= counts_as_call;
165168
}
166169
self.complete = true;
167170
}
@@ -184,7 +187,7 @@ impl Future {
184187
mem::drop(state);
185188
callback.call();
186189
} else {
187-
state.callbacks.push(callback);
190+
state.callbacks.push((true, callback));
188191
}
189192
}
190193

@@ -212,10 +215,11 @@ impl<'a> StdFuture for Future {
212215
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
213216
let mut state = self.state.lock().unwrap();
214217
if state.complete {
218+
state.callbacks_made = true;
215219
Poll::Ready(())
216220
} else {
217221
let waker = cx.waker().clone();
218-
state.callbacks.push(Box::new(StdWaker(waker)));
222+
state.callbacks.push((false, Box::new(StdWaker(waker))));
219223
Poll::Pending
220224
}
221225
}
@@ -433,4 +437,36 @@ mod tests {
433437
assert_eq!(Pin::new(&mut future).poll(&mut Context::from_waker(&waker)), Poll::Ready(()));
434438
assert_eq!(Pin::new(&mut second_future).poll(&mut Context::from_waker(&second_waker)), Poll::Ready(()));
435439
}
440+
441+
#[test]
442+
fn test_dropped_future_doesnt_count() {
443+
// Tests that if a Future gets drop'd before it is poll()ed `Ready` it doesn't count as
444+
// having been woken, leaving the notify-required flag set.
445+
let notifier = Notifier::new();
446+
notifier.notify();
447+
448+
// If we get a future and don't touch it we're definitely still notify-required.
449+
notifier.get_future();
450+
assert!(notifier.wait_timeout(Duration::from_millis(1)));
451+
assert!(!notifier.wait_timeout(Duration::from_millis(1)));
452+
453+
// Even if we poll'd once but didn't observe a `Ready`, we should be notify-required.
454+
let mut future = notifier.get_future();
455+
let (woken, waker) = create_waker();
456+
assert_eq!(Pin::new(&mut future).poll(&mut Context::from_waker(&waker)), Poll::Pending);
457+
458+
notifier.notify();
459+
assert!(woken.load(Ordering::SeqCst));
460+
assert!(notifier.wait_timeout(Duration::from_millis(1)));
461+
462+
// However, once we do poll `Ready` it should wipe the notify-required flag.
463+
let mut future = notifier.get_future();
464+
let (woken, waker) = create_waker();
465+
assert_eq!(Pin::new(&mut future).poll(&mut Context::from_waker(&waker)), Poll::Pending);
466+
467+
notifier.notify();
468+
assert!(woken.load(Ordering::SeqCst));
469+
assert_eq!(Pin::new(&mut future).poll(&mut Context::from_waker(&waker)), Poll::Ready(()));
470+
assert!(!notifier.wait_timeout(Duration::from_millis(1)));
471+
}
436472
}

0 commit comments

Comments
 (0)