Add a new HappyEyeballsConnectionImpl class#17371
Add a new HappyEyeballsConnectionImpl class#17371alyssawilk merged 51 commits intoenvoyproxy:mainfrom
Conversation
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
|
/assign @RenjieTang |
|
/assign @alyssawilk |
alyssawilk
left a comment
There was a problem hiding this comment.
Huzzah for happy eyeballs! I a high level first pass but I think it's going to result in some churn so I'll do a more thorough pass after those changes land.
/wait
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
|
/retest |
|
Retrying Azure Pipelines: |
Signed-off-by: Ryan Hamilton <rch@google.com>
snowp
left a comment
There was a problem hiding this comment.
Thanks this looks great overall, just a few comments mostly around comments :)
| bool startSecureTransport() override; | ||
| absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override; | ||
|
|
||
| // Simple getters which always delegate to the first connection. |
There was a problem hiding this comment.
First connection that we attempted to create? Or "first" connection still active (first in some list?)?
There was a problem hiding this comment.
First connection in connection_. I've tweaked the comment accordingly.
| * underlying connection. Before the connection is established, however | ||
| * their behavior depends on their semantics. For anything which can result |
There was a problem hiding this comment.
Before the connection is established, however their behavior depends on their semantics. reads a tad bit awkward, maybe we can rephrase it or just change the punctuation?
There was a problem hiding this comment.
Oh, good point. I think it's missing a comma after "however", but I've tweaked it slightly to make it less awkward (I hope!).
| for (auto it = connections_.begin(); it != connections_.end();) { | ||
| if (it->get() == &(wrapper->connection())) { | ||
| (*it)->close(ConnectionCloseType::NoFlush); | ||
| it = connections_.erase(it); | ||
| } else { | ||
| ++it; | ||
| } | ||
| } |
There was a problem hiding this comment.
How come we remove the connection callbacks up higher in setUpFinalConnection when we close connections but not here?
There was a problem hiding this comment.
So this method, cleanupWrapperAndConnection() is only called from onEvent(). The first thing that onEvent() does it remove the wrapper connection callbacks from the connection. So that means that by the time we're in the body of cleanupWrapperAndConnection() the callbacks have already been removed. Does that sound right?
There was a problem hiding this comment.
I think that sounds right. Anything we can do to make this clearer in the code? From reading this I was wondering if this was a bug
Maybe extract function with a bool param to decide if we should remove the callbacks or just a comment somewhere?
There was a problem hiding this comment.
Thanks for digging into this. Yeah, I agree this is not awesome so I'm happy to make it better... Just not quite sure what that should look like.
One thing we could do would be to move the current call to removeConnectionCallbacks() from the opt of onEvent() to the top of cleanupWrapperAndConnection(). Then we can call removeConnectionCallbacks for all connections in setUpFinalConnection(). I think this ends up with a net readability win since the remove calls are closer to the other related logic?
I've done this is the most recent commit. WDYT?
| // Called by the wrapper when the wrapped connection is above the write buffer | ||
| // high water mark. | ||
| void onAboveWriteBufferHighWatermark(ConnectionCallbacksWrapper* wrapper); | ||
|
|
||
| // Called by the wrapper when the wrapped connection is above the write buffer | ||
| // high water mark. | ||
| void onBelowWriteBufferLowWatermark(ConnectionCallbacksWrapper* wrapper); |
There was a problem hiding this comment.
These have NOT_REACHED macros in the impl, so are these not actually called at all? Comments seem a bit misleading
There was a problem hiding this comment.
Fair question. I wasn't quite sure where to put this. Basically since we never write data to the connection while the wrapper is still set as the connection callbacks, it should be impossible for this method to be called. But it is reachable via the code on line 107. That being said, I've removed these methods and added the NOT_REACHED to the wrapper methods, along with a comment. Does that help?
| static Network::ClientConnectionPtr | ||
| createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& cluster, | ||
| const Network::Address::InstanceConstSharedPtr& address, | ||
| const std::vector<Network::Address::InstanceConstSharedPtr>& address_list, |
There was a problem hiding this comment.
What is the distinction between the address param and the address_list param? Could they be consolidated?
There was a problem hiding this comment.
So this is a bit of a work in progress. We have another piece of work to actually populate this in production and I suspect we'll end up tweaking the API here, but I'm not sure. I'd be inclined to punt the consolidate to the follow up PR.
That being said, the idea here is that traditionally Envoy creates a connection to a single IP, of course. The Envoy HostImpl object has a single address which is used when creating a connection. This address comes from the host resolution which happened before the HostImpl was created. We're going to change this so that host resolution optionally ends up providing the full list of addresses to the HostImpl. We'd also like to run v4 (A records) and v6 (AAAA records) resolution in parallel and delivering the results as they arrive. This seems likely to require a tweak to the API involved, but we'll get to that when we get there :)
Ok, so to your specific question... Today a host always has an address and sometimes has an addressList (in the tests which call setAddressList). We could have this method simply take an address list and treat the first entry as the main address. The downside is that this would require an extra vector allocation in the case where the HostImpl itself has no address list. I don't love this idea but it's not the end of the world. Happy to do it if you think it's best?
There was a problem hiding this comment.
It sounds a bit hairy so let's save that until we want to properly support a list of addresses, thanks for explaining!
Signed-off-by: Ryan Hamilton <rch@google.com>
RyanTheOptimist
left a comment
There was a problem hiding this comment.
Thanks for the review!
| * underlying connection. Before the connection is established, however | ||
| * their behavior depends on their semantics. For anything which can result |
There was a problem hiding this comment.
Oh, good point. I think it's missing a comma after "however", but I've tweaked it slightly to make it less awkward (I hope!).
| bool startSecureTransport() override; | ||
| absl::optional<std::chrono::milliseconds> lastRoundTripTime() const override; | ||
|
|
||
| // Simple getters which always delegate to the first connection. |
There was a problem hiding this comment.
First connection in connection_. I've tweaked the comment accordingly.
| // Called by the wrapper when the wrapped connection is above the write buffer | ||
| // high water mark. | ||
| void onAboveWriteBufferHighWatermark(ConnectionCallbacksWrapper* wrapper); | ||
|
|
||
| // Called by the wrapper when the wrapped connection is above the write buffer | ||
| // high water mark. | ||
| void onBelowWriteBufferLowWatermark(ConnectionCallbacksWrapper* wrapper); |
There was a problem hiding this comment.
Fair question. I wasn't quite sure where to put this. Basically since we never write data to the connection while the wrapper is still set as the connection callbacks, it should be impossible for this method to be called. But it is reachable via the code on line 107. That being said, I've removed these methods and added the NOT_REACHED to the wrapper methods, along with a comment. Does that help?
| for (auto it = connections_.begin(); it != connections_.end();) { | ||
| if (it->get() == &(wrapper->connection())) { | ||
| (*it)->close(ConnectionCloseType::NoFlush); | ||
| it = connections_.erase(it); | ||
| } else { | ||
| ++it; | ||
| } | ||
| } |
There was a problem hiding this comment.
So this method, cleanupWrapperAndConnection() is only called from onEvent(). The first thing that onEvent() does it remove the wrapper connection callbacks from the connection. So that means that by the time we're in the body of cleanupWrapperAndConnection() the callbacks have already been removed. Does that sound right?
| static Network::ClientConnectionPtr | ||
| createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& cluster, | ||
| const Network::Address::InstanceConstSharedPtr& address, | ||
| const std::vector<Network::Address::InstanceConstSharedPtr>& address_list, |
There was a problem hiding this comment.
So this is a bit of a work in progress. We have another piece of work to actually populate this in production and I suspect we'll end up tweaking the API here, but I'm not sure. I'd be inclined to punt the consolidate to the follow up PR.
That being said, the idea here is that traditionally Envoy creates a connection to a single IP, of course. The Envoy HostImpl object has a single address which is used when creating a connection. This address comes from the host resolution which happened before the HostImpl was created. We're going to change this so that host resolution optionally ends up providing the full list of addresses to the HostImpl. We'd also like to run v4 (A records) and v6 (AAAA records) resolution in parallel and delivering the results as they arrive. This seems likely to require a tweak to the API involved, but we'll get to that when we get there :)
Ok, so to your specific question... Today a host always has an address and sometimes has an addressList (in the tests which call setAddressList). We could have this method simply take an address list and treat the first entry as the main address. The downside is that this would require an extra vector allocation in the case where the HostImpl itself has no address list. I don't love this idea but it's not the end of the world. Happy to do it if you think it's best?
snowp
left a comment
There was a problem hiding this comment.
Thanks, at this point the main thing I'm missing is making it clear about when we remove the connection callbacks
| for (auto it = connections_.begin(); it != connections_.end();) { | ||
| if (it->get() == &(wrapper->connection())) { | ||
| (*it)->close(ConnectionCloseType::NoFlush); | ||
| it = connections_.erase(it); | ||
| } else { | ||
| ++it; | ||
| } | ||
| } |
There was a problem hiding this comment.
I think that sounds right. Anything we can do to make this clearer in the code? From reading this I was wondering if this was a bug
Maybe extract function with a bool param to decide if we should remove the callbacks or just a comment somewhere?
| static Network::ClientConnectionPtr | ||
| createConnection(Event::Dispatcher& dispatcher, const ClusterInfo& cluster, | ||
| const Network::Address::InstanceConstSharedPtr& address, | ||
| const std::vector<Network::Address::InstanceConstSharedPtr>& address_list, |
There was a problem hiding this comment.
It sounds a bit hairy so let's save that until we want to properly support a list of addresses, thanks for explaining!
Signed-off-by: Ryan Hamilton <rch@google.com>
RyanTheOptimist
left a comment
There was a problem hiding this comment.
Thanks for the insightful comments.
| for (auto it = connections_.begin(); it != connections_.end();) { | ||
| if (it->get() == &(wrapper->connection())) { | ||
| (*it)->close(ConnectionCloseType::NoFlush); | ||
| it = connections_.erase(it); | ||
| } else { | ||
| ++it; | ||
| } | ||
| } |
There was a problem hiding this comment.
Thanks for digging into this. Yeah, I agree this is not awesome so I'm happy to make it better... Just not quite sure what that should look like.
One thing we could do would be to move the current call to removeConnectionCallbacks() from the opt of onEvent() to the top of cleanupWrapperAndConnection(). Then we can call removeConnectionCallbacks for all connections in setUpFinalConnection(). I think this ends up with a net readability win since the remove calls are closer to the other related logic?
I've done this is the most recent commit. WDYT?
|
/retest |
|
Retrying Azure Pipelines: |
|
sorry for coverage you may need to pull main |
Signed-off-by: Ryan Hamilton <rch@google.com>
…bridge-stream * upstream/main: stream info: add upstream connection id (envoyproxy#17512) runtime: removing require_ocsp_response_for_must_staple_certs (envoyproxy#17541) tooling: Fixes/cleanups/tests for `rst_checks.py` (envoyproxy#17589) Add a new HappyEyeballsConnectionImpl class (envoyproxy#17371) hcm/listener_manager: remove deprecated v2 config handling. (envoyproxy#17586) tools: simplify bazel deps query (envoyproxy#17599) tooling: Move tempdir/cleanup to base runner class (envoyproxy#17590) tools: fix missing format strings (envoyproxy#17600) tooling: Add `@envoy` prefix in `envoy_py_` macros (envoyproxy#17588) stream info: add attempt count for HTTP requests and TCP proxy (envoyproxy#17583) Signed-off-by: Garrett Bourg <bourg@squareup.com>
Add a new HappyEyeballsConnectionImpl class This is an implementation of Network::ClientConnection which is constructed with a list of IP address instead of a single address. The class will potentially create an underlying connection for each address and use the first connection which actually succeeds. Additional Description: N/A Risk Level: None - Unused code Testing: Unit tests Docs Changes: N/A Release Notes: N/A Platform Specific Features: N/A Signed-off-by: Ryan Hamilton <rch@google.com>
Add a new HappyEyeballsConnectionImpl class
This is an implementation of Network::ClientConnection which is constructed with a list of IP address instead of a single address. The class will potentially create an underlying connection for each address and use the first connection which actually succeeds.
Additional Description: N/A
Risk Level: None - Unused code
Testing: Unit tests
Docs Changes: N/A
Release Notes: N/A
Platform Specific Features: N/A