Skip to content

Add a new HappyEyeballsConnectionImpl class#17371

Merged
alyssawilk merged 51 commits intoenvoyproxy:mainfrom
RyanTheOptimist:HappyEyeballs
Aug 5, 2021
Merged

Add a new HappyEyeballsConnectionImpl class#17371
alyssawilk merged 51 commits intoenvoyproxy:mainfrom
RyanTheOptimist:HappyEyeballs

Conversation

@RyanTheOptimist
Copy link
Contributor

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>
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>
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>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
@RyanTheOptimist
Copy link
Contributor Author

/assign @RenjieTang

Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Ryan Hamilton <rch@google.com>
RenjieTang
RenjieTang previously approved these changes Jul 16, 2021
@RyanTheOptimist
Copy link
Contributor Author

/assign @alyssawilk

Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
alyssawilk
alyssawilk previously approved these changes Jul 27, 2021
Signed-off-by: Ryan Hamilton <rch@google.com>
alyssawilk
alyssawilk previously approved these changes Jul 27, 2021
Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:-)

@RyanTheOptimist
Copy link
Contributor Author

/retest

@repokitteh-read-only
Copy link

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #17371 (comment) was created by @RyanTheOptimist.

see: more, trace.

Signed-off-by: Ryan Hamilton <rch@google.com>
Copy link
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First connection that we attempted to create? Or "first" connection still active (first in some list?)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First connection in connection_. I've tweaked the comment accordingly.

Comment on lines +24 to +25
* underlying connection. Before the connection is established, however
* their behavior depends on their semantics. For anything which can result
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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!).

Comment on lines +527 to +534
for (auto it = connections_.begin(); it != connections_.end();) {
if (it->get() == &(wrapper->connection())) {
(*it)->close(ConnectionCloseType::NoFlush);
it = connections_.erase(it);
} else {
++it;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we remove the connection callbacks up higher in setUpFinalConnection when we close connections but not here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment on lines +137 to +143
// 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These have NOT_REACHED macros in the impl, so are these not actually called at all? Comments seem a bit misleading

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the distinction between the address param and the address_list param? Could they be consolidated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Signed-off-by: Ryan Hamilton <rch@google.com>
Copy link
Contributor Author

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!

Comment on lines +24 to +25
* underlying connection. Before the connection is established, however
* their behavior depends on their semantics. For anything which can result
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First connection in connection_. I've tweaked the comment accordingly.

Comment on lines +137 to +143
// 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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Comment on lines +527 to +534
for (auto it = connections_.begin(); it != connections_.end();) {
if (it->get() == &(wrapper->connection())) {
(*it)->close(ConnectionCloseType::NoFlush);
it = connections_.erase(it);
} else {
++it;
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, at this point the main thing I'm missing is making it clear about when we remove the connection callbacks

Comment on lines +527 to +534
for (auto it = connections_.begin(); it != connections_.end();) {
if (it->get() == &(wrapper->connection())) {
(*it)->close(ConnectionCloseType::NoFlush);
it = connections_.erase(it);
} else {
++it;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Contributor Author

@RyanTheOptimist RyanTheOptimist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the insightful comments.

Comment on lines +527 to +534
for (auto it = connections_.begin(); it != connections_.end();) {
if (it->get() == &(wrapper->connection())) {
(*it)->close(ConnectionCloseType::NoFlush);
it = connections_.erase(it);
} else {
++it;
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@RyanTheOptimist
Copy link
Contributor Author

/retest

@repokitteh-read-only
Copy link

Retrying Azure Pipelines:
Retried failed jobs in: envoy-presubmit

🐱

Caused by: a #17371 (comment) was created by @RyanTheOptimist.

see: more, trace.

@alyssawilk
Copy link
Contributor

sorry for coverage you may need to pull main

Signed-off-by: Ryan Hamilton <rch@google.com>
Copy link
Contributor

@snowp snowp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

@alyssawilk alyssawilk merged commit a19a9e0 into envoyproxy:main Aug 5, 2021
baojr added a commit to baojr/envoy that referenced this pull request Aug 5, 2021
…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>
leyao-daily pushed a commit to leyao-daily/envoy that referenced this pull request Sep 30, 2021
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants