Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

sync: tweak watch API #2814

Merged
merged 5 commits into from Sep 11, 2020
Merged

sync: tweak watch API #2814

merged 5 commits into from Sep 11, 2020

Conversation

carllerche
Copy link
Member

@carllerche carllerche commented Sep 4, 2020

Decouples getting the latest watch value from receiving the change notification. The Receiver async method becomes Receiver::changed(). The latest value is obtained from Receiver::borrow().

The implementation is updated to use Notify. This requires adding Notify::notify_waiters. This method is generally useful but is kept private for now.

Decouples getting the latest `watch` value from receiving the change
notification. The `Receiver` async method becomes
`Receiver::changed()`. The latest value is obtained from
`Receiver::borrow()`.

The implementation is updated to use `Notify`. This requires adding
`Notify::notify_waiters`. This method is generally useful but is kept
private for now.
@carllerche carllerche added this to the v0.3 milestone Sep 4, 2020
Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

this is really nice --- the new API is great and i'm pleased by how simple the implementation is now. i had a few questions and minor nits.

tokio/src/sync/notify.rs Outdated Show resolved Hide resolved
tokio/src/sync/notify.rs Outdated Show resolved Hide resolved
tokio/src/sync/notify.rs Show resolved Hide resolved
tokio/src/sync/watch.rs Outdated Show resolved Hide resolved
// not memory access.
if 1 == self.shared.ref_count_rx.fetch_sub(1, Relaxed) {
// This is the last `Receiver` handle, tasks waiting on `Sender::closed()`
self.shared.notify_tx.notify_waiters();
Copy link
Member

Choose a reason for hiding this comment

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

notify_waiters can theoretically panic (if, e.g. the mutex is poisoned). should we change it so that it can't panic, since it's called in Drop impls?

Copy link
Member Author

@carllerche carllerche Sep 9, 2020

Choose a reason for hiding this comment

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

I don't think it can panic unless there is a bug internal to Watch. IMO we only need to defend against runtime conditions and not bugs (at the module boundary).

tokio/src/sync/watch.rs Outdated Show resolved Hide resolved
tokio/src/sync/watch.rs Outdated Show resolved Hide resolved
tokio/src/sync/watch.rs Outdated Show resolved Hide resolved
}

// There are waiters, the lock must be acquired to notify.
let mut waiters = self.waiters.lock().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

it looks like we call notify_waiters in drop impls in a couple places. should this not panic if the lock is poisoned?

i think we still want to remove the waiters regardless of whether the lock is poisoned, so this should probably be

Suggested change
let mut waiters = self.waiters.lock().unwrap();
let mut waiters = self.waiters.lock().unwrap_or_else(PoisonError::into_inner);

since parking_lot doesn't have poisoning, the current code is already equivalent to this for a lot of production users...

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not really worried about lock poisoning here. In general, I rather pretend lock poisoning isn't a thing for internal usage. This is generally how I think about Mutex across the code base. I guess we should update our internal Mutex shim to abort the process instead of panic then? That would be a separate PR?

Copy link
Member

Choose a reason for hiding this comment

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

I guess we should update our internal Mutex shim to abort the process instead of panic then? That would be a separate PR?

I think it's better to update the internal mutex shim to make locking infallible by ignoring poisoned mutex errors via into_inner. This would result in essentially the same behavior as we get with parking_lot regardless of whether or not the parking_lot feature is enabled. Aborting the process any time a mutex is poisoned could potentially make a lot of errors, including in user code, much harder to debug.

tokio/src/sync/watch.rs Show resolved Hide resolved
@Darksonn Darksonn added A-tokio Area: The main tokio crate C-enhancement Category: A PR with an enhancement or bugfix. M-sync Module: tokio/sync labels Sep 5, 2020
use loom::thread;

#[test]
fn smoke() {
Copy link
Member Author

Choose a reason for hiding this comment

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

This test catches the issues that were mentioned by @hawkw


// Polling the future once is guaranteed to return `Pending` as `watch`
// only notifies using `notify_waiters`.
crate::future::poll_fn(|cx| {
Copy link
Member Author

Choose a reason for hiding this comment

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

This forces a waiter to be pushed by Notify. I thought about moving it into a fn, but I opted to punt.

Copy link
Member

Choose a reason for hiding this comment

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

Yup, this seems fine for now!

Copy link
Member

@hawkw hawkw left a comment

Choose a reason for hiding this comment

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

I still think that if we are going to ignore mutex poisoning, we should be doing so by calling LockResult::into_inner to get a MutexGuard even if the lock is poisoned, rather than unwrap()ing. But, we should probably make that change everywhere, so it doesn't have to be fixed in this PR.

Everything looks good to me! Thanks for the new test.


// Polling the future once is guaranteed to return `Pending` as `watch`
// only notifies using `notify_waiters`.
crate::future::poll_fn(|cx| {
Copy link
Member

Choose a reason for hiding this comment

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

Yup, this seems fine for now!

@carllerche
Copy link
Member Author

@hawkw I don't disagree w/ you w.r.t mutex poisoning. I'm just saying it should be done in one swoop across all of Tokio in a single PR :). I'm mostly just following the current style. Feel free to open a tracking issue for the mutex poisoning and assign it to 0.3.

@carllerche carllerche merged commit 2bc9a48 into master Sep 11, 2020
@carllerche carllerche deleted the rework-watch branch September 11, 2020 22:14
@hawkw
Copy link
Member

hawkw commented Sep 11, 2020

@hawkw I don't disagree w/ you w.r.t mutex poisoning. I'm just saying it should be done in one swoop across all of Tokio in a single PR :). I'm mostly just following the current style. Feel free to open a tracking issue for the mutex poisoning and assign it to 0.3.

Yup, we're in alignment on this one --- as I said in my comment, we should probably make this change globally. :)

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 C-enhancement Category: A PR with an enhancement or bugfix. M-sync Module: tokio/sync
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants