mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-08 00:00:13 +02:00
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.
This commit is contained in:
+20
-13
@@ -106,6 +106,14 @@ pub struct Notify {
|
|||||||
waiters: Mutex<WaitList>,
|
waiters: Mutex<WaitList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum NotificationType {
|
||||||
|
// Notification triggered by calling `notify_waiters`
|
||||||
|
AllWaiters,
|
||||||
|
// Notification triggered by calling `notify_one`
|
||||||
|
OneWaiter,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Waiter {
|
struct Waiter {
|
||||||
/// Intrusive linked-list pointers
|
/// Intrusive linked-list pointers
|
||||||
@@ -115,7 +123,7 @@ struct Waiter {
|
|||||||
waker: Option<Waker>,
|
waker: Option<Waker>,
|
||||||
|
|
||||||
/// `true` if the notification has been assigned to this waiter.
|
/// `true` if the notification has been assigned to this waiter.
|
||||||
notified: bool,
|
notified: Option<NotificationType>,
|
||||||
|
|
||||||
/// Should not be `Unpin`.
|
/// Should not be `Unpin`.
|
||||||
_p: PhantomPinned,
|
_p: PhantomPinned,
|
||||||
@@ -230,7 +238,7 @@ impl Notify {
|
|||||||
waiter: UnsafeCell::new(Waiter {
|
waiter: UnsafeCell::new(Waiter {
|
||||||
pointers: linked_list::Pointers::new(),
|
pointers: linked_list::Pointers::new(),
|
||||||
waker: None,
|
waker: None,
|
||||||
notified: false,
|
notified: None,
|
||||||
_p: PhantomPinned,
|
_p: PhantomPinned,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
@@ -327,9 +335,9 @@ impl Notify {
|
|||||||
// Safety: `waiters` lock is still held.
|
// Safety: `waiters` lock is still held.
|
||||||
let waiter = unsafe { waiter.as_mut() };
|
let waiter = unsafe { waiter.as_mut() };
|
||||||
|
|
||||||
assert!(!waiter.notified);
|
assert!(waiter.notified.is_none());
|
||||||
|
|
||||||
waiter.notified = true;
|
waiter.notified = Some(NotificationType::AllWaiters);
|
||||||
|
|
||||||
if let Some(waker) = waiter.waker.take() {
|
if let Some(waker) = waiter.waker.take() {
|
||||||
waker.wake();
|
waker.wake();
|
||||||
@@ -375,9 +383,9 @@ fn notify_locked(waiters: &mut WaitList, state: &AtomicU8, curr: u8) -> Option<W
|
|||||||
// Safety: `waiters` lock is still held.
|
// Safety: `waiters` lock is still held.
|
||||||
let waiter = unsafe { waiter.as_mut() };
|
let waiter = unsafe { waiter.as_mut() };
|
||||||
|
|
||||||
assert!(!waiter.notified);
|
assert!(waiter.notified.is_none());
|
||||||
|
|
||||||
waiter.notified = true;
|
waiter.notified = Some(NotificationType::OneWaiter);
|
||||||
let waker = waiter.waker.take();
|
let waker = waiter.waker.take();
|
||||||
|
|
||||||
if waiters.is_empty() {
|
if waiters.is_empty() {
|
||||||
@@ -506,11 +514,11 @@ impl Future for Notified<'_> {
|
|||||||
// Safety: called while locked
|
// Safety: called while locked
|
||||||
let w = unsafe { &mut *waiter.get() };
|
let w = unsafe { &mut *waiter.get() };
|
||||||
|
|
||||||
if w.notified {
|
if w.notified.is_some() {
|
||||||
// Our waker has been notified. Reset the fields and
|
// Our waker has been notified. Reset the fields and
|
||||||
// remove it from the list.
|
// remove it from the list.
|
||||||
w.waker = None;
|
w.waker = None;
|
||||||
w.notified = false;
|
w.notified = None;
|
||||||
|
|
||||||
*state = Done;
|
*state = Done;
|
||||||
} else {
|
} else {
|
||||||
@@ -582,14 +590,13 @@ impl Drop for Notified<'_> {
|
|||||||
notify.state.store(EMPTY, SeqCst);
|
notify.state.store(EMPTY, SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See if the node was notified but not received. In this case, the
|
// See if the node was notified but not received. In this case, if
|
||||||
// notification must be sent to another waiter.
|
// the notification was triggered via `notify_one`, it must be sent
|
||||||
|
// to the next waiter.
|
||||||
//
|
//
|
||||||
// Safety: with the entry removed from the linked list, there can be
|
// Safety: with the entry removed from the linked list, there can be
|
||||||
// no concurrent access to the entry
|
// no concurrent access to the entry
|
||||||
let notified = unsafe { (*waiter.get()).notified };
|
if let Some(NotificationType::OneWaiter) = unsafe { (*waiter.get()).notified } {
|
||||||
|
|
||||||
if notified {
|
|
||||||
if let Some(waker) = notify_locked(&mut waiters, ¬ify.state, notify_state) {
|
if let Some(waker) = notify_locked(&mut waiters, ¬ify.state, notify_state) {
|
||||||
drop(waiters);
|
drop(waiters);
|
||||||
waker.wake();
|
waker.wake();
|
||||||
|
|||||||
@@ -6,14 +6,30 @@ use loom::thread;
|
|||||||
#[test]
|
#[test]
|
||||||
fn smoke() {
|
fn smoke() {
|
||||||
loom::model(|| {
|
loom::model(|| {
|
||||||
let (tx, mut rx) = watch::channel(1);
|
let (tx, mut rx1) = watch::channel(1);
|
||||||
|
let mut rx2 = rx1.clone();
|
||||||
|
let mut rx3 = rx1.clone();
|
||||||
|
let mut rx4 = rx1.clone();
|
||||||
|
let mut rx5 = rx1.clone();
|
||||||
|
|
||||||
let th = thread::spawn(move || {
|
let th = thread::spawn(move || {
|
||||||
tx.send(2).unwrap();
|
tx.send(2).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
block_on(rx.changed()).unwrap();
|
block_on(rx1.changed()).unwrap();
|
||||||
assert_eq!(*rx.borrow(), 2);
|
assert_eq!(*rx1.borrow(), 2);
|
||||||
|
|
||||||
|
block_on(rx2.changed()).unwrap();
|
||||||
|
assert_eq!(*rx2.borrow(), 2);
|
||||||
|
|
||||||
|
block_on(rx3.changed()).unwrap();
|
||||||
|
assert_eq!(*rx3.borrow(), 2);
|
||||||
|
|
||||||
|
block_on(rx4.changed()).unwrap();
|
||||||
|
assert_eq!(*rx4.borrow(), 2);
|
||||||
|
|
||||||
|
block_on(rx5.changed()).unwrap();
|
||||||
|
assert_eq!(*rx5.borrow(), 2);
|
||||||
|
|
||||||
th.join().unwrap();
|
th.join().unwrap();
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user