From dbad721b30fcbeb8339068e8bc10c20b3af97a6f Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 01:07:04 +0000 Subject: [PATCH 1/9] ready-cache: Ensure cancelation updates can be observed `tokio::task` enforces a cooperative scheduling regime that can cause `oneshot::Receiver::poll` to return pending after the sender has sent an update. `ReadyCache` uses a oneshot to notify pending services that they should not become ready. When a cancelation is not observed, the ready cache return service instances that should have been canceled, which breaks assumptions and causes an invalid state. Fixes #415 Co-authored-by: Eliza Weisman --- tower/src/ready_cache/cache.rs | 106 +++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index f0fc1d053..974ce7ae3 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -75,7 +75,9 @@ where // Safety: This is safe because we do not use `Pin::new_unchecked`. impl Unpin for ReadyCache {} -type CancelRx = oneshot::Receiver<()>; +/// The cancelation receiver must not participate in task budgetting: we need it +/// to return ready as soon as the sender notifies it. +type CancelRx = tokio::task::Unconstrained>; type CancelTx = oneshot::Sender<()>; type CancelPair = (CancelTx, CancelRx); @@ -117,15 +119,9 @@ where S: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Self { - pending, - pending_cancel_txs, - ready, - } = self; f.debug_struct("ReadyCache") - .field("pending", pending) - .field("pending_cancel_txs", pending_cancel_txs) - .field("ready", ready) + .field("pending", &self.pending) + .field("pending_cancel_txs", &self.pending_cancel_txs) .finish() } } @@ -224,8 +220,13 @@ where /// /// [`poll_pending`]: crate::ready_cache::cache::ReadyCache::poll_pending pub fn push(&mut self, key: K, svc: S) { - let cancel = oneshot::channel(); - self.push_pending(key, svc, cancel); + let (tx, rx) = oneshot::channel(); + + // Cancelations MUST be observed as soon as the sender notifies it so + // that the pending service cannot drive a canceled service to ready. + let rx = tokio::task::unconstrained(rx); + + self.push_pending(key, svc, (tx, rx)); } fn push_pending(&mut self, key: K, svc: S, (cancel_tx, cancel_rx): CancelPair) { @@ -268,21 +269,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 return an error must have a pending cancelation" + ); } } Poll::Ready(Some(Err(PendingError::Canceled(_)))) => { @@ -292,13 +282,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 +398,10 @@ where type Output = Result<(K, S, CancelRx), PendingError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - 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. + let mut cancel = self.cancel.as_mut().expect("polled after complete"); + 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(); @@ -443,16 +433,40 @@ where S: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let Self { - key, - cancel, - ready, - _pd, - } = self; - f.debug_struct("Pending") - .field("key", key) - .field("cancel", cancel) - .field("ready", ready) - .finish() + f.debug_struct("Pending").field("key", &self.key).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.get_mut().0.poll_pending(cx) + } + } + Ready(cache).await.unwrap(); } } From 163ca2b33d754167cd58ddcc50c9794e7b5fcb42 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 15:32:32 +0000 Subject: [PATCH 2/9] Simplify use of unconstrained to restore Debug impls --- tower/src/ready_cache/cache.rs | 44 +++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 974ce7ae3..0f83389df 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -75,9 +75,7 @@ where // Safety: This is safe because we do not use `Pin::new_unchecked`. impl Unpin for ReadyCache {} -/// The cancelation receiver must not participate in task budgetting: we need it -/// to return ready as soon as the sender notifies it. -type CancelRx = tokio::task::Unconstrained>; +type CancelRx = oneshot::Receiver<()>; type CancelTx = oneshot::Sender<()>; type CancelPair = (CancelTx, CancelRx); @@ -119,9 +117,15 @@ where S: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + pending, + pending_cancel_txs, + ready, + } = self; f.debug_struct("ReadyCache") - .field("pending", &self.pending) - .field("pending_cancel_txs", &self.pending_cancel_txs) + .field("pending", pending) + .field("pending_cancel_txs", pending_cancel_txs) + .field("ready", ready) .finish() } } @@ -220,13 +224,8 @@ where /// /// [`poll_pending`]: crate::ready_cache::cache::ReadyCache::poll_pending pub fn push(&mut self, key: K, svc: S) { - let (tx, rx) = oneshot::channel(); - - // Cancelations MUST be observed as soon as the sender notifies it so - // that the pending service cannot drive a canceled service to ready. - let rx = tokio::task::unconstrained(rx); - - self.push_pending(key, svc, (tx, rx)); + let cancel = oneshot::channel(); + self.push_pending(key, svc, cancel); } fn push_pending(&mut self, key: K, svc: S, (cancel_tx, cancel_rx): CancelPair) { @@ -399,8 +398,11 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // This MUST return ready as soon as the sender has been notified so - // that we don't return a service that has been canceled. - let mut cancel = self.cancel.as_mut().expect("polled after complete"); + // 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")); 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"); @@ -433,7 +435,17 @@ where S: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Pending").field("key", &self.key).finish() + let Self { + key, + cancel, + ready, + _pd, + } = self; + f.debug_struct("Pending") + .field("key", key) + .field("cancel", cancel) + .field("ready", ready) + .finish() } } @@ -448,7 +460,7 @@ mod test { let mut handles = vec![]; // NOTE This test passes at 129 items, but fails at 130 items (if the - // cancelation receiver is not marked `Unconstrained`). + // cancelation receiver is not marked `unconstrained`). for _ in 0..130 { let (svc, mut handle) = tower_test::mock::pair::<(), ()>(); handle.allow(1); From 2740c233a0b0759aac5bf21020fc3535b70ddfaf Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 15:48:29 +0000 Subject: [PATCH 3/9] fixup assertion --- tower/src/ready_cache/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 0f83389df..64791ea8a 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -270,7 +270,7 @@ where } else { assert!( cancel_tx.is_some(), - "services that return an error must have a pending cancelation" + "services that become ready must have a pending cancelation" ); } } From daaa28547582d9bb8a495b3ca935c81d33daecff Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 16:22:38 +0000 Subject: [PATCH 4/9] Use an AtomicBool and Notify @carllerche points out that, even with `unconstrained`, there are no guarantees that a `oneshot::Receiver` will observe the sent value immediately, even when using `tokio::task::unconstrained`. --- tower/src/ready_cache/cache.rs | 83 +++++++++++++++++++++++++++------- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 64791ea8a..3092a1ed3 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -9,8 +9,10 @@ use std::fmt; use std::future::Future; use std::hash::Hash; use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::task::{Context, Poll}; -use tokio::sync::oneshot; +use tokio::sync::Notify; use tower_service::Service; use tracing::{debug, trace}; @@ -75,8 +77,18 @@ where // Safety: This is safe because we do not use `Pin::new_unchecked`. impl Unpin for ReadyCache {} -type CancelRx = oneshot::Receiver<()>; -type CancelTx = oneshot::Sender<()>; +#[derive(Debug)] +struct Cancel { + notify: Notify, + canceled: AtomicBool, +} + +#[derive(Debug)] +struct CancelRx(Arc); + +#[derive(Debug)] +struct CancelTx(Arc); + type CancelPair = (CancelTx, CancelRx); #[derive(Debug)] @@ -193,7 +205,7 @@ where /// must be called to cause the service to be dropped. pub fn evict>(&mut self, key: &Q) -> bool { let canceled = if let Some(c) = self.pending_cancel_txs.swap_remove(key) { - c.send(()).expect("cancel receiver lost"); + c.cancel(); true } else { false @@ -224,14 +236,14 @@ where /// /// [`poll_pending`]: crate::ready_cache::cache::ReadyCache::poll_pending pub fn push(&mut self, key: K, svc: S) { - let cancel = oneshot::channel(); + let cancel = cancelable(); self.push_pending(key, svc, cancel); } fn push_pending(&mut self, key: K, svc: S, (cancel_tx, cancel_rx): CancelPair) { if let Some(c) = self.pending_cancel_txs.insert(key.clone(), cancel_tx) { // If there is already a service for this key, cancel it. - c.send(()).expect("cancel receiver lost"); + c.cancel(); } self.pending.push(Pending { key: Some(key), @@ -385,6 +397,28 @@ where } } +// === impl Cancel === + +/// Creates a cancelation sender and receiver. +/// +/// A `tokio::sync::oneshot` is NOT used, as a `Receiver` is not guaranteed to +/// observe results as soon as a `Sender` fires. Using an `AtomicBool` allows +/// the state to be observed as soon as the cancelation is triggered. +fn cancelable() -> CancelPair { + let cx = Arc::new(Cancel { + notify: Notify::new(), + canceled: AtomicBool::new(false), + }); + (CancelTx(cx.clone()), CancelRx(cx)) +} + +impl CancelTx { + fn cancel(self) { + self.0.canceled.store(true, Ordering::SeqCst); + self.0.notify.notify_waiters(); + } +} + // === Pending === // Safety: No use unsafe access therefore this is safe. @@ -397,25 +431,40 @@ where type Output = Result<(K, S, CancelRx), PendingError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // 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")); - if let Poll::Ready(r) = Pin::new(&mut cancel).poll(cx) { - assert!(r.is_ok(), "cancel sender lost"); + // Before checking whether a service ready, check to see whether + // readiness has been canceled. + let CancelRx(cancel) = self.cancel.as_mut().expect("polled after complete"); + if cancel.canceled.load(Ordering::SeqCst) { let key = self.key.take().expect("polled after complete"); return Err(PendingError::Canceled(key)).into(); } + // If readiness hasn't been canceled, check to see whether the service + // is ready. match self .ready .as_mut() .expect("polled after ready") .poll_ready(cx) { - Poll::Pending => Poll::Pending, + Poll::Pending => { + // Before returning Pending, register interest in cancelation so + // that this future is polled again if the state changes. + let CancelRx(cancel) = self.cancel.as_mut().expect("polled after complete"); + tokio::pin! { + let cancel_notified = cancel.notify.notified(); + } + let crx_ready = cancel_notified.poll(cx); + // Because both the cancel receiver and cancel sender are held + // by the `ReadyCache` (i.e., on a single task), then it must + // not be possible for the cancelation state to change while + // polling. + assert!( + crx_ready.is_pending() && !cancel.canceled.load(Ordering::SeqCst), + "cancelation cannot be notified while polling a pending service" + ); + Poll::Pending + } Poll::Ready(Ok(())) => { let key = self.key.take().expect("polled after complete"); let cancel = self.cancel.take().expect("polled after complete"); @@ -459,8 +508,8 @@ mod test { 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`). + // NOTE This test passes at 129 items, but fails at 130 items (if coop + // schedulding interferes with cancelation). for _ in 0..130 { let (svc, mut handle) = tower_test::mock::pair::<(), ()>(); handle.allow(1); From 16f081e656f7fe277d460392e75e3eca05b8a61c Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 16:37:07 +0000 Subject: [PATCH 5/9] Move tests out of module Signed-off-by: Oliver Gould --- tower/src/ready_cache/cache.rs | 34 --------------------------------- tower/tests/ready_cache/main.rs | 32 ++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 3092a1ed3..982e2046b 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -497,37 +497,3 @@ 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 coop - // schedulding interferes with cancelation). - 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.get_mut().0.poll_pending(cx) - } - } - Ready(cache).await.unwrap(); - } -} diff --git a/tower/tests/ready_cache/main.rs b/tower/tests/ready_cache/main.rs index f7acbf81c..56be6a9cb 100644 --- a/tower/tests/ready_cache/main.rs +++ b/tower/tests/ready_cache/main.rs @@ -2,8 +2,9 @@ #[path = "../support.rs"] mod support; +use std::pin::Pin; use tokio_test::{assert_pending, assert_ready, task}; -use tower::ready_cache::ReadyCache; +use tower::ready_cache::{error, ReadyCache}; use tower_test::mock; type Req = &'static str; @@ -191,3 +192,32 @@ fn duplicate_key_by_index() { // _and_ service 0 should now be callable assert!(task.enter(|cx, _| cache.check_ready(cx, &0)).unwrap()); } + +// 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 coop + // schedulding interferes with cancelation). + 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.get_mut().0.poll_pending(cx) + } + } + Ready(cache).await.unwrap(); +} From efd199d7b81216965df04b2c48b8bed591c5a756 Mon Sep 17 00:00:00 2001 From: Eliza Weisman Date: Fri, 17 Jun 2022 10:01:32 -0700 Subject: [PATCH 6/9] ready_cache: use `futures::task::AtomicWaker` for cancelation (#669) This replaces the use of `tokio::sync::Notify` with `futures::task::AtomicWaker`. `Notify` requires the `Notified` future to be held in order to remain interested in the notification. Dropping the future returned by `Notify::notified` cancels interest in the notification. So, the current code using `Notify` is incorrect, as the `Notified` future is created on each poll and then immediately dropped, releasing interest in the wakeup. We could solve this by storing the `Notify::notified` future in the `Pending` future, but this would be a bit of a pain, as the `Notified` future borrows the `Notify`. I thought that it was a nicer alternative to just rewrite this code to use `AtomicWaker` instead. Also, `AtomicWaker` is a much simpler, lower-level primitive. It simply stores a single waker that can wake up a single task. `Notify` is capable of waking multiple tasks, either in order or all at once, which makes it more complex. --- tower/src/ready_cache/cache.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 982e2046b..b265ddcdc 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -2,7 +2,7 @@ use super::error; use futures_core::Stream; -use futures_util::stream::FuturesUnordered; +use futures_util::{stream::FuturesUnordered, task::AtomicWaker}; pub use indexmap::Equivalent; use indexmap::IndexMap; use std::fmt; @@ -12,7 +12,6 @@ use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::task::{Context, Poll}; -use tokio::sync::Notify; use tower_service::Service; use tracing::{debug, trace}; @@ -79,7 +78,7 @@ impl Unpin for ReadyCache {} #[derive(Debug)] struct Cancel { - notify: Notify, + waker: AtomicWaker, canceled: AtomicBool, } @@ -406,7 +405,7 @@ where /// the state to be observed as soon as the cancelation is triggered. fn cancelable() -> CancelPair { let cx = Arc::new(Cancel { - notify: Notify::new(), + waker: AtomicWaker::new(), canceled: AtomicBool::new(false), }); (CancelTx(cx.clone()), CancelRx(cx)) @@ -415,7 +414,7 @@ fn cancelable() -> CancelPair { impl CancelTx { fn cancel(self) { self.0.canceled.store(true, Ordering::SeqCst); - self.0.notify.notify_waiters(); + self.0.waker.wake(); } } @@ -451,16 +450,13 @@ where // Before returning Pending, register interest in cancelation so // that this future is polled again if the state changes. let CancelRx(cancel) = self.cancel.as_mut().expect("polled after complete"); - tokio::pin! { - let cancel_notified = cancel.notify.notified(); - } - let crx_ready = cancel_notified.poll(cx); + cancel.waker.register(cx.waker()); // Because both the cancel receiver and cancel sender are held // by the `ReadyCache` (i.e., on a single task), then it must // not be possible for the cancelation state to change while - // polling. + // polling a `Pending` service. assert!( - crx_ready.is_pending() && !cancel.canceled.load(Ordering::SeqCst), + !cancel.canceled.load(Ordering::SeqCst), "cancelation cannot be notified while polling a pending service" ); Poll::Pending From babe1107b94ab1cb7ac0252fb088f7d1263aefb2 Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 17:03:44 +0000 Subject: [PATCH 7/9] typo --- tower/src/ready_cache/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index b265ddcdc..5ce5a2dd0 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -430,7 +430,7 @@ where type Output = Result<(K, S, CancelRx), PendingError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Before checking whether a service ready, check to see whether + // Before checking whether a service is ready, check to see whether // readiness has been canceled. let CancelRx(cancel) = self.cancel.as_mut().expect("polled after complete"); if cancel.canceled.load(Ordering::SeqCst) { From 1e737963948ccaaef00f0b2c7b9f8f19778c474b Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 17:05:40 +0000 Subject: [PATCH 8/9] typo --- tower/src/ready_cache/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 5ce5a2dd0..a5b1fbc90 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -430,7 +430,7 @@ where type Output = Result<(K, S, CancelRx), PendingError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Before checking whether a service is ready, check to see whether + // Before checking whether the service is ready, check to see whether // readiness has been canceled. let CancelRx(cancel) = self.cancel.as_mut().expect("polled after complete"); if cancel.canceled.load(Ordering::SeqCst) { From 26b5f1566e876e4b6f17fff496fb926d431b993e Mon Sep 17 00:00:00 2001 From: Oliver Gould Date: Fri, 17 Jun 2022 18:41:03 +0000 Subject: [PATCH 9/9] clippy Signed-off-by: Oliver Gould --- tower/src/ready_cache/cache.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tower/src/ready_cache/cache.rs b/tower/src/ready_cache/cache.rs index 4cd9366ac..8070d7807 100644 --- a/tower/src/ready_cache/cache.rs +++ b/tower/src/ready_cache/cache.rs @@ -428,7 +428,7 @@ where { type Output = Result<(K, S, CancelRx), PendingError>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); // Before checking whether the service is ready, check to see whether // readiness has been canceled.