-
Notifications
You must be signed in to change notification settings - Fork 1k
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
add h2::Error as a source
for tonic::Status when converting from h2::Error
#612
Conversation
This could also be simplified to just have |
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.
Nice! I think this makes sense 😊 I had a couple of suggestions. Just minor things.
This could also be simplified to just have Status carry a h2_reason field and not support any of the std::error::Error::source functionality. (There would be a h2_reason method returning an Optionh2::Reason).
I actually prefer the implementation in this PR as that we wont need special methods for each new kind of error source we might add in the future.
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.
I think this looks good but should hear from @LucioFranco as well.
@tdyas do you wanna drive this home? We would like to include the change to |
I modified the PR to add a generic source field, although my Rust knowledge is not as advanced so not sure what the right invocation to use to fix this error:
|
This seems to do the trick impl Error for Status {
fn source(&self) -> Option<&(dyn Error + 'static)> {
self.source.as_ref().map(|err| (&**err) as _)
}
}
Phew 😨 Removing Clone on Status causes a test in prost.rs to fail but that can be fixed with let messages = std::iter::repeat_with(move || Ok::<_, Status>(msg.clone())).take(10000); |
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.
Changing my review "comment" since I wanna give it final look when things are compiling.
Edit: Which apparently github doesn't let you do... How do you go from "approved" to "not approved yet" without "request changes" 🤷
Not sure but I can hit the re-request review button to reset that state (which I just did). |
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.
I think it looks good! I had one question and an integration test would be cool as well.
tonic/src/status.rs
Outdated
.source() | ||
.and_then(|err| err.downcast_ref::<h2::Error>()) | ||
.unwrap(); | ||
assert_eq!(orig.reason(), source.reason()); |
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.
I'm thinking it would also be nice to have a high level integration test kinda thing that uses the APIs users would. Not sure how to write such a test though.
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.
The h2
crate has test helpers for low level testing of HTTP/2 streams. Maybe this integration test would be similar to the tests in https://github.com/hyperium/h2/tree/master/tests/h2-tests?
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.
Any thoughts on whether an h2-level integration test would be the way to go?
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.
I don't think we need those low level helpers, we should be able to trigger most things from hyper/h2 directly, no?
@tdyas @davidpdrsn any updates on this? |
I haven't had a chance to work on an integration test for this due to my work schedule. I see there is now a merge conflict, in trying to fix that, I am having a compile error in
|
I rebased the PR and now it fails with:
Are those tests relying on |
Also, I had to add |
Yes we should still downcast that error correctly. I did some digging and this fixes it: diff --git a/tonic/src/status.rs b/tonic/src/status.rs
index ef9731a..927d95b 100644
--- a/tonic/src/status.rs
+++ b/tonic/src/status.rs
@@ -320,12 +320,6 @@ impl Status {
Err(err) => err,
};
- #[cfg(feature = "transport")]
- let err = match err.downcast::<crate::transport::TimeoutExpired>() {
- Ok(timeout) => return Ok(Status::cancelled(timeout.to_string())),
- Err(err) => err,
- };
-
#[cfg(feature = "transport")]
let err = match err.downcast::<h2::Error>() {
Ok(h2) => {
@@ -555,6 +549,11 @@ fn find_status_in_source_chain(err: &(dyn Error + 'static)) -> Option<Status> {
});
}
+ #[cfg(feature = "transport")]
+ if let Some(timeout) = err.downcast_ref::<crate::transport::TimeoutExpired>() {
+ return Some(Status::cancelled(timeout.to_string()));
+ }
+
source = err.source();
}
Nah I don't think there is another way. Its fine 👌 |
Pushed @davidpdrsn's fix for the timeout error decoding. |
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.
@tdyas Thank you for your excellent work on this! I only had one minor comment/question. So close! 😃
tonic/src/status.rs
Outdated
impl TryFrom<Box<dyn Error + Send + Sync + 'static>> for Status { | ||
type Error = Box<dyn Error + Send + Sync + 'static>; | ||
|
||
fn try_from(err: Box<dyn Error + Send + Sync + 'static>) -> Result<Self, Self::Error> { | ||
Status::try_from_error(err) | ||
} | ||
} | ||
|
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.
Is there a specific use-case this impl enables, or is mainly for convenience? I dunno, feels a bit out of place for me.
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.
I don't recall, and I can't find a relevant use of this in the main code bases in which I work. Shall I remove it?
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.
Yeah I think we should just remove it.
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.
LGTM!
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.
LGTM, just need to get CI working then we can merge! Thanks for pushing this across the finish line!
tonic/src/status.rs
Outdated
@@ -356,7 +355,13 @@ impl Status { | |||
_ => Code::Unknown, | |||
}; | |||
|
|||
Status::new(code, format!("h2 protocol error: {}", err)) | |||
let mut status = Self::new(code, format!("h2 protocol error: {}", err)); | |||
let error: Option<Box<dyn Error + Send + Sync + 'static>> = err |
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.
why do you need this extra type hint?
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.
I don't recall. I removed it and it still compiles so pushed the removal of it.
tonic/src/status.rs
Outdated
@@ -3,6 +3,7 @@ use crate::metadata::MetadataMap; | |||
use bytes::Bytes; | |||
use http::header::{HeaderMap, HeaderValue}; | |||
use percent_encoding::{percent_decode, percent_encode, AsciiSet, CONTROLS}; | |||
use std::convert::TryFrom; |
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.
Looks like this is failing CI?
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.
Looking.
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.
Fixed.
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.
It seems like deny(warnings)
is only set in CI? When I run cargo clippy
, it only warned for this and some other issues. It would probably be useful to make the crates enable #![deny(warnings)]
(but in a separate PR obviously).
I believe the official GRPC-on-h2 spec says that this should be mapped by the library to a UNAVAILABLE code; the user of the library shouldn't have to deal with recognizing it. https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#goaway-frame |
Motivation
A gRPC server may send a HTTP/2 GOAWAY frame with NO_ERROR status to gracefully shutdown a connection. This appears to Tonic users as a
tonic::Status
withCode::Internal
and the message set toh2 protocol error: protocol error: not a result of an error
.The only way to currently detect this case and differentiate it from other internal errors (e.g., an application-level internal error) is to match on the message. A client may want to differentiate these cases because it may only want to alert on the application-level internal error and not on the transient transport-level issue. (Indeed, this is the use case for which I'm envisioning using this change.)
Matching on a message is not as robust, however, as matching on an
h2::Error
and its reason code. (The message could change for example if a future version of Tonic decided to vary the message. This would break any users that matched on the previous version of the message.)Solution
Store the
h2::Error
used when creating atonic::Status
from ah2::Error
and provide it as thesource
for purposes ofstd::error::Error
. This will allow users to downcast it and match on the originalh2::Reason
.Note:
tonic::Status
must now manually implementClone
becauseh2::Error
cannot implementClone
since one of theKind
variants stores astd::io::Error
(which does not implementClone
). Since we know thath2_error
will always have a reason since we only store it in that case, this manual implementation ofClone
just extracts the reason and creates a newh2::Error
from it to "clone" it.