-
Notifications
You must be signed in to change notification settings - Fork 283
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
ready-cache: Ensure cancelation can be observed #668
Merged
Merged
Changes from 3 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
dbad721
ready-cache: Ensure cancelation updates can be observed
olix0r 163ca2b
Simplify use of unconstrained to restore Debug impls
olix0r 2740c23
fixup assertion
olix0r daaa285
Use an AtomicBool and Notify
olix0r 16f081e
Move tests out of module
olix0r efd199d
ready_cache: use `futures::task::AtomicWaker` for cancelation (#669)
hawkw babe110
typo
olix0r 1e73796
typo
olix0r e79b331
Merge branch 'master' into ver/rc-notify
hawkw 26b5f15
clippy
olix0r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -268,21 +268,10 @@ where | |||||||||||||||||||||||||
// recreated after the service is used. | ||||||||||||||||||||||||||
self.ready.insert(key, (svc, (cancel_tx, cancel_rx))); | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
// This should not technically be possible. We must have decided to cancel | ||||||||||||||||||||||||||
// a Service (by sending on the CancelTx), yet that same service then | ||||||||||||||||||||||||||
// returns Ready. Since polling a Pending _first_ polls the CancelRx, that | ||||||||||||||||||||||||||
// _should_ always see our CancelTx send. Yet empirically, that isn't true: | ||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||
// https://github.com/tower-rs/tower/issues/415 | ||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||
// So, we instead detect the endpoint as canceled at this point. That | ||||||||||||||||||||||||||
// should be fine, since the oneshot is only really there to ensure that | ||||||||||||||||||||||||||
// the Pending is polled again anyway. | ||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||
// We assert that this can't happen in debug mode so that hopefully one day | ||||||||||||||||||||||||||
// we can find a test that triggers this reliably. | ||||||||||||||||||||||||||
debug_assert!(cancel_tx.is_some()); | ||||||||||||||||||||||||||
debug!("canceled endpoint removed when ready"); | ||||||||||||||||||||||||||
assert!( | ||||||||||||||||||||||||||
cancel_tx.is_some(), | ||||||||||||||||||||||||||
"services that become ready must have a pending cancelation" | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Poll::Ready(Some(Err(PendingError::Canceled(_)))) => { | ||||||||||||||||||||||||||
|
@@ -292,13 +281,11 @@ where | |||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Poll::Ready(Some(Err(PendingError::Inner(key, e)))) => { | ||||||||||||||||||||||||||
let cancel_tx = self.pending_cancel_txs.swap_remove(&key); | ||||||||||||||||||||||||||
if cancel_tx.is_some() { | ||||||||||||||||||||||||||
return Err(error::Failed(key, e.into())).into(); | ||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||
// See comment for the same clause under Ready(Some(Ok)). | ||||||||||||||||||||||||||
debug_assert!(cancel_tx.is_some()); | ||||||||||||||||||||||||||
debug!("canceled endpoint removed on error"); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
assert!( | ||||||||||||||||||||||||||
cancel_tx.is_some(), | ||||||||||||||||||||||||||
"services that return an error must have a pending cancelation" | ||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
return Err(error::Failed(key, e.into())).into(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
@@ -410,8 +397,13 @@ where | |||||||||||||||||||||||||
type Output = Result<(K, S, CancelRx), PendingError<K, S::Error>>; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { | ||||||||||||||||||||||||||
let mut fut = self.cancel.as_mut().expect("polled after complete"); | ||||||||||||||||||||||||||
if let Poll::Ready(r) = Pin::new(&mut fut).poll(cx) { | ||||||||||||||||||||||||||
// This MUST return ready as soon as the sender has been notified so | ||||||||||||||||||||||||||
// that we don't return a service that has been canceled, so we disable | ||||||||||||||||||||||||||
// cooperative scheduling on the receiver. Otherwise, the receiver can | ||||||||||||||||||||||||||
// sporadically return pending even though the sender has fired. | ||||||||||||||||||||||||||
let mut cancel = | ||||||||||||||||||||||||||
tokio::task::unconstrained(self.cancel.as_mut().expect("polled after complete")); | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style nit, take it or leave it: might consider something like this, so that it's very obvious that the comment is referring to the use of
Suggested change
|
||||||||||||||||||||||||||
if let Poll::Ready(r) = Pin::new(&mut cancel).poll(cx) { | ||||||||||||||||||||||||||
assert!(r.is_ok(), "cancel sender lost"); | ||||||||||||||||||||||||||
let key = self.key.take().expect("polled after complete"); | ||||||||||||||||||||||||||
return Err(PendingError::Canceled(key)).into(); | ||||||||||||||||||||||||||
|
@@ -456,3 +448,37 @@ where | |||||||||||||||||||||||||
.finish() | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
#[cfg(test)] | ||||||||||||||||||||||||||
mod test { | ||||||||||||||||||||||||||
use super::*; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// Tests https://github.com/tower-rs/tower/issues/415 | ||||||||||||||||||||||||||
#[tokio::test(flavor = "current_thread")] | ||||||||||||||||||||||||||
async fn cancelation_observed() { | ||||||||||||||||||||||||||
let mut cache = ReadyCache::default(); | ||||||||||||||||||||||||||
let mut handles = vec![]; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
// NOTE This test passes at 129 items, but fails at 130 items (if the | ||||||||||||||||||||||||||
// cancelation receiver is not marked `unconstrained`). | ||||||||||||||||||||||||||
for _ in 0..130 { | ||||||||||||||||||||||||||
let (svc, mut handle) = tower_test::mock::pair::<(), ()>(); | ||||||||||||||||||||||||||
handle.allow(1); | ||||||||||||||||||||||||||
cache.push("ep0", svc); | ||||||||||||||||||||||||||
handles.push(handle); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
struct Ready(ReadyCache<&'static str, tower_test::mock::Mock<(), ()>, ()>); | ||||||||||||||||||||||||||
impl Unpin for Ready {} | ||||||||||||||||||||||||||
impl std::future::Future for Ready { | ||||||||||||||||||||||||||
type Output = Result<(), error::Failed<&'static str>>; | ||||||||||||||||||||||||||
fn poll( | ||||||||||||||||||||||||||
self: Pin<&mut Self>, | ||||||||||||||||||||||||||
cx: &mut std::task::Context<'_>, | ||||||||||||||||||||||||||
) -> std::task::Poll<Self::Output> { | ||||||||||||||||||||||||||
self.get_mut().0.poll_pending(cx) | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
Ready(cache).await.unwrap(); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
olix0r marked this conversation as resolved.
Show resolved
Hide resolved
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tiny nit:
it's all cooperative scheduling :)