Skip to content

Commit e7091fd

Browse files
authored
sync: Remove readiness assertion in `watch::Receiver::changed() (#2839)
*In `watch::Receiver::changed` `Notified` was polled for the first time to ensure the waiter is registered while assuming that the first poll will always return `Pending`. It is the case however that another instance of `Notified` is dropped without receiving its notification, this "orphaned" notification can be used to satisfy another waiter without even registering it. This commit accounts for that scenario.
1 parent 2348f67 commit e7091fd

File tree

2 files changed

+39
-16
lines changed

2 files changed

+39
-16
lines changed

tokio/src/sync/notify.rs

+20-13
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ pub struct Notify {
106106
waiters: Mutex<WaitList>,
107107
}
108108

109+
#[derive(Debug, Clone, Copy)]
110+
enum NotificationType {
111+
// Notification triggered by calling `notify_waiters`
112+
AllWaiters,
113+
// Notification triggered by calling `notify_one`
114+
OneWaiter,
115+
}
116+
109117
#[derive(Debug)]
110118
struct Waiter {
111119
/// Intrusive linked-list pointers
@@ -115,7 +123,7 @@ struct Waiter {
115123
waker: Option<Waker>,
116124

117125
/// `true` if the notification has been assigned to this waiter.
118-
notified: bool,
126+
notified: Option<NotificationType>,
119127

120128
/// Should not be `Unpin`.
121129
_p: PhantomPinned,
@@ -230,7 +238,7 @@ impl Notify {
230238
waiter: UnsafeCell::new(Waiter {
231239
pointers: linked_list::Pointers::new(),
232240
waker: None,
233-
notified: false,
241+
notified: None,
234242
_p: PhantomPinned,
235243
}),
236244
}
@@ -327,9 +335,9 @@ impl Notify {
327335
// Safety: `waiters` lock is still held.
328336
let waiter = unsafe { waiter.as_mut() };
329337

330-
assert!(!waiter.notified);
338+
assert!(waiter.notified.is_none());
331339

332-
waiter.notified = true;
340+
waiter.notified = Some(NotificationType::AllWaiters);
333341

334342
if let Some(waker) = waiter.waker.take() {
335343
waker.wake();
@@ -375,9 +383,9 @@ fn notify_locked(waiters: &mut WaitList, state: &AtomicU8, curr: u8) -> Option<W
375383
// Safety: `waiters` lock is still held.
376384
let waiter = unsafe { waiter.as_mut() };
377385

378-
assert!(!waiter.notified);
386+
assert!(waiter.notified.is_none());
379387

380-
waiter.notified = true;
388+
waiter.notified = Some(NotificationType::OneWaiter);
381389
let waker = waiter.waker.take();
382390

383391
if waiters.is_empty() {
@@ -506,11 +514,11 @@ impl Future for Notified<'_> {
506514
// Safety: called while locked
507515
let w = unsafe { &mut *waiter.get() };
508516

509-
if w.notified {
517+
if w.notified.is_some() {
510518
// Our waker has been notified. Reset the fields and
511519
// remove it from the list.
512520
w.waker = None;
513-
w.notified = false;
521+
w.notified = None;
514522

515523
*state = Done;
516524
} else {
@@ -582,14 +590,13 @@ impl Drop for Notified<'_> {
582590
notify.state.store(EMPTY, SeqCst);
583591
}
584592

585-
// See if the node was notified but not received. In this case, the
586-
// notification must be sent to another waiter.
593+
// See if the node was notified but not received. In this case, if
594+
// the notification was triggered via `notify_one`, it must be sent
595+
// to the next waiter.
587596
//
588597
// Safety: with the entry removed from the linked list, there can be
589598
// no concurrent access to the entry
590-
let notified = unsafe { (*waiter.get()).notified };
591-
592-
if notified {
599+
if let Some(NotificationType::OneWaiter) = unsafe { (*waiter.get()).notified } {
593600
if let Some(waker) = notify_locked(&mut waiters, &notify.state, notify_state) {
594601
drop(waiters);
595602
waker.wake();

tokio/src/sync/tests/loom_watch.rs

+19-3
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,30 @@ use loom::thread;
66
#[test]
77
fn smoke() {
88
loom::model(|| {
9-
let (tx, mut rx) = watch::channel(1);
9+
let (tx, mut rx1) = watch::channel(1);
10+
let mut rx2 = rx1.clone();
11+
let mut rx3 = rx1.clone();
12+
let mut rx4 = rx1.clone();
13+
let mut rx5 = rx1.clone();
1014

1115
let th = thread::spawn(move || {
1216
tx.send(2).unwrap();
1317
});
1418

15-
block_on(rx.changed()).unwrap();
16-
assert_eq!(*rx.borrow(), 2);
19+
block_on(rx1.changed()).unwrap();
20+
assert_eq!(*rx1.borrow(), 2);
21+
22+
block_on(rx2.changed()).unwrap();
23+
assert_eq!(*rx2.borrow(), 2);
24+
25+
block_on(rx3.changed()).unwrap();
26+
assert_eq!(*rx3.borrow(), 2);
27+
28+
block_on(rx4.changed()).unwrap();
29+
assert_eq!(*rx4.borrow(), 2);
30+
31+
block_on(rx5.changed()).unwrap();
32+
assert_eq!(*rx5.borrow(), 2);
1733

1834
th.join().unwrap();
1935
})

0 commit comments

Comments
 (0)