-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
rfc: Make DestinationServiceQuery generic #749
Conversation
Additionally, `UpdateRx` is now `Receiver`. `DestinationServiceQuery::connect_maybe` has been moved onto `DiscoveryWork`, where it is called from.
Make Remote and Receiver generic over the response future and body, instead of the http service.
To further simplify `control::discovery`, add a public API onto Remote, hiding the internal enum representation. Furthermore, Receiver now _only_ exposes a Stream and not its internal state.
54772e6
to
f586c09
Compare
I understand that the goal here is to enable other implementations of service discovery with equivalent functionality to the Destination service. I think a good way to validate this design would be to refactor the DNS-based service discovery to implement this interface. Or, if that's practical and we think DNS is special in some way, then we should document why we didn't redo DNS in this mold. |
I agree, but I think we ought to do this in a separate PR. |
Not exactly. The goal is to enable interacting with arbitrary gRPC streaming endpoints. |
proxy/src/control/remote_stream.rs
Outdated
pub struct Remote<M, F, B: Body = RecvBody>(Inner<M, F, B>); | ||
|
||
#[derive(Debug)] | ||
enum Inner<M, F, B: Body = RecvBody> { |
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 is it necessary to have separate Remote
and Inner
types? Usually we would have an Inner
type if if we have an Option<Inner>
that we sometimes take()
, but that's not the case here. If we can combine this into Remote
, let's do so, to make the code easier to read. Otherwise, let's add a comment here about why it is split out.
proxy/src/control/discovery.rs
Outdated
/// Initiates a query `query` to the Destination service and returns it as | ||
/// `Some(query)` if the given authority's host is of a form suitable for using to | ||
/// query the Destination service. Otherwise, returns `None`. | ||
pub fn connect_maybe( |
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.
This shouldn't be pub
, AFAICT.
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.
We should rename this function to connect_to_destination_service_maybe()
since it is now in a more general context than before.
proxy/src/control/discovery.rs
Outdated
@@ -62,49 +63,11 @@ pub struct DiscoveryWork<T: HttpService<ResponseBody = RecvBody>> { | |||
|
|||
struct DestinationSet<T: HttpService<ResponseBody = RecvBody>> { | |||
addrs: Exists<Cache<SocketAddr, ()>>, | |||
query: Option<DestinationServiceQuery<T>>, | |||
query: Option<Remote<PbUpdate, T::Future, T::ResponseBody>>, |
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 we should have a type alias type DestinationServiceQuery<T> = Remote<PbUpdate, T::Future, T::ResponseBody>
and use it in this module instead of Remote
directly. Otherwise IMO the code would be harder to understand than it is now.
proxy/src/control/discovery.rs
Outdated
None => (None, Exists::Unknown), | ||
Some(query) => { | ||
match query.into_receiver_maybe() { | ||
None => (Some(Remote::new()), Exists::Unknown), |
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 would be much clearer to just make the Remote
enum public with public variants so that we can match on it directly. This code here only makes sense if Remote::new()
is equivalent to what query
was originally, which means we're not gaining anything by hiding the structure of Remote
since, if there was a third variant of that enum, or if the enum had any different structure, this code would almost definitely be wrong.
proxy/src/control/discovery.rs
Outdated
}) | ||
} | ||
} | ||
|
||
|
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.
Let's lose the extra whitespace changes.
@@ -0,0 +1,122 @@ | |||
|
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.
No blank line up here.
proxy/src/control/remote_stream.rs
Outdated
M: Message + Default, | ||
B: Body<Data = Data>, | ||
F: Future<Item = http::Response<B>>, | ||
{ |
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.
Again, here we're creating a big API with a bunch of boilerplate for, AFAICT, no reason, since there's no way to implement or use this API correctly unless Remote
is an enum isomorphic to Some<Receiver<M, F, B>>
. It would be better to lose all this boilerplate and make the code a lot easier to understand.
@briansmith per your feedback, I've minimized the API change so that there's lesser impact to discovery. The Remote type is now generic over the HttpService instead of its Future and body types -- this simplifies type signature complexity. |
proxy/src/control/discovery.rs
Outdated
client, | ||
vac.key(), | ||
"connect"); | ||
let query = Self::query_destination_service_if_valid_auth( |
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.
valid_auth
seems like a poor name to me. Really the distinction isn't "valid" vs "invalid" but rather "may be an internal service" vs "is definitely not an internal service". I suggest query_destination_service_if_relevant
or similar. In particular I would like to avoid implying anything about the validity of the authority in the name.
@@ -462,10 +427,10 @@ where | |||
for (auth, set) in &mut self.destinations { | |||
// Query the Destination service first. | |||
let (new_query, found_by_destination_service) = match set.query.take() { | |||
Some(DestinationServiceQuery::ConnectedOrConnecting{ rx }) => { | |||
Some(Remote::ConnectedOrConnecting{ rx }) => { |
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.
TIL we can't refer to this as DestinationServiceQuery
because of rust-lang/rust#26264. (No change necessary.)
proxy/src/control/discovery.rs
Outdated
}, | ||
Ok(Async::NotReady) => { | ||
return (DestinationServiceQuery::ConnectedOrConnecting { rx }, exists); | ||
return (Remote::connected(rx), exists); |
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.
connected()
is too misleading since the connection might not be connected yet. I suggest we just use ConnectedOrConnecting
directly to make it clearer.
proxy/src/control/remote_stream.rs
Outdated
} | ||
} | ||
|
||
pub fn connected(rx: Receiver<M, S>) -> Self { |
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.
If you take my suggestion above then we should just remove connected()
.
proxy/src/control/remote_stream.rs
Outdated
Rx::Streaming(ref mut stream) => { | ||
return stream.poll().map_err(|e| grpc::Error::Grpc(match e { | ||
grpc::Error::Inner(()) => grpc::Status::UNKNOWN, | ||
grpc::Error::Grpc(status) => status, |
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 understand what the goal is with the remapping here and I found the structure confusing.
I suggest:
return stream.poll().map_err(|e| {
match e {
grpc::Error::Inner(()) =>
// <Document here why we are doing this.>
grpc::Error::Grpc(grpc::Status::UNKNOWN),
e => e,
}
});
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 underlying issue here is the following:
grpc::Error<T>
is generic overT
: the underlying future's failure (forgrpc::Error::Inner
)stream.poll
can't actually fail with an Inner error, so it returns angrpc::Error<()>
- here, we want to coerce the stream's error response to the general grpc error, so we need to drop the () type and convert to a T type.
- This is why
e => e
doesn't work here -- the types are actually different.
i'll comment this a bit at least.
@briansmith feedback addressed |
The goals of this change are: 1. Reduce the size/complexity of `control::discovery` in order to ease code reviews. 2. Extract a reusable grpc streaming utility. There are no intended functional changes. `control::discovery::DestinationServiceQuery` is used to track the state of a request (and streaming response) to the destination service. Very little of this logic is specific to the destination service. The `DestinationServiceQuery` and associated `UpdateRx` type have been moved to a new module, `control::remote_stream`, as `Remote` and `Receiver`, respectively. Both of these types are generic over the gRPC message type, so it will be possible to use this utility with additional API endpoints. The `Receiver::poll` implementation has been simplified to be more idiomatic with the rest of our code (namely, using `try_ready!`).
The goals of this change are:
control::discovery
in order to ease code reviews.There are no intended functional changes.
control::discovery::DestinationServiceQuery
is used to track the state of a request (and streaming response) to the destination service. Very little of this logic is specific to the destination service.The
DestinationServiceQuery
and associatedUpdateRx
type have been moved to a new module,control::remote_stream
, asRemote
andReceiver
, respectively. Both of these types are generic over the gRPC message type, so it will be possible to use this utility with additional API endpoints.The
Receiver::poll
implementation has been simplified to be more idiomatic with the rest of our code (namely, usingtry_ready!
).Additionally, the enum states are now hidden behind an API.
Remote
now exposes constructors, aneeds_reconnect()
method that indicates whether the remote is disconnected, and ainto_receiver_maybe()
that consumes the remote, producing a receiver if one exists.Note: It may help to review the individual commits in order.