-
Notifications
You must be signed in to change notification settings - Fork 281
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
Combining a tower Service with a tonic client panics #547
Comments
Hm yeah that does look odd. I'm not very familiar with this part of the code but seems odd to me that @hawkw Do you know? |
impl Service<Request<BoxBody>> for Svc {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, req: Request<BoxBody>) -> Self::Future {
let mut inner = self.0.clone();
Box::pin(async move {
inner.call(req).await.map_err(Into::into)
})
}
} This subtly breaks the contract. The service is driven to ready and then cloned before it is invoked. The original service is ready, but the clone is not necessarily ready. To fix this, the call function could be rewritten as: Box::pin(self.0.call(req).err_into::<Error>()) which avoids the cloning. If cloning is really necessary, you could use |
Ah I see, that's unfortunate this accidentally worked. I initially thought of the |
Uhh thats really subtle and not something I had thought of. I think makes sense to mention it in the |
Some more context for people who might discover this in the future: I have tried implementing a version of It is currently done using a semaphore but could in theory also be done using The current implementation gets around this by We could just ignore I think the "token" solution suggested here could fix this. We would probably be able to make |
Has anyone looked into a fix for this here in tower, or in tonic? |
is there an issue with using impl Service<Request<BoxBody>> for Svc {
type Response = Response<Body>;
type Error = Box<dyn std::error::Error + Send + Sync>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.0.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, req: Request<BoxBody>) -> Self::Future {
let mut inner = self.0.clone();
Box::pin(async move {
std::future::poll_fn(|cx| inner.poll_ready(cx))
.await
.map_err(Into::into)
.and_then(|_| inner.call(req).await.map_err(Into::into))
})
}
} I guess I should mention that my real use case is implementing retries within fn call(&mut self, req: Request<BoxBody>) -> Self::Future {
let mut inner = self.0.clone();
Box::pin(async move {
for _ in 0..3 {
std::future::poll_fn(|cx| inner.poll_ready(cx))
.await
.map_err(Into::into)
.and_then(|_| inner.call(req).await.map_err(Into::into))
}
})
} This is stop-gap for a more fully baked version of ergonomic retries: #682 and a tonic version that relies on http with clonable requests (see hyperium/tonic#733). |
Hi, I get the following while updating my deps to tokio v1 and tonic/tower along with it.
Can confirm that
poll_ready
was in fact called and returnedPoll::Ready(Ok(()))
beforecall
was invoked.Looking at the code, it seems related to cloning a Semaphore, which resets to
State::Empty
so thatcall
is now always invoked withState::Empty
even though a permit was acquired successfully? Or maybe something's fundamentally changed with 0.4 re how this works?Included here a small example scenario that hits this issue and hopefully illustrates the setup:
The text was updated successfully, but these errors were encountered: