-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
io_handle: cache envoy address instances for recvmsg and recvmmsg #34649
Conversation
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Throwing over to @RyanTheOptimist but please get !googler review once you're done |
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
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.
Thanks for doing this. For some reason I thought we could do this at the QUIC layer, but after looking at your PR, obviously we have to do it at the socket layer. I think this looks pretty good. Do we have any sort of benchmark we can run to ensure that this does what we want?
envoy/network/socket_interface.h
Outdated
@@ -15,8 +17,14 @@ struct SocketCreationOptions { | |||
// and only valid on Linux. | |||
bool mptcp_enabled_{false}; | |||
|
|||
// Specifies whether there should be a cache for all the address instances associated to this |
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 rephrase this slightly:
// Specifies the maximum size of the cache of the address instances associated with
// packets received by this socket. If this is 0, no addresses will be cached. Is only
// valid for datagram sockets.
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.
done
false, /*max_addresses_cache_size_ = */ ( | ||
Runtime::runtimeFeatureEnabled( | ||
"envoy.reloadable_features.quic_upstream_socket_use_address_cache_for_read") | ||
? 4u |
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.
Out of curiosity, why 4? Is this same method use for client and server? (The runtime feature says "upstream" so maybe this is client-only?)
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 is only used to create client socket. I would think 4 is sufficient for a quic client connection, with a default path and an alternative path.
Runtime::runtimeFeatureEnabled( | ||
"envoy.reloadable_features.quic_upstream_socket_use_address_cache_for_read") | ||
? 4u | ||
: 0u)}); |
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.
nit: Up to you, but consider factoring this out into a small helper method:
int getSocketAddressCacheSize() {
if (Runtime::runtimeFeatureEnabled(
"envoy.reloadable_features.quic_upstream_socket_use_address_cache_for_read") {
return 4;
}
return 0;
}
This would avoid the need for the /*max_addresses_cache_size_ = */ (
which is a bit hard to parse mentally. Instead we would simply say:
Network::SocketCreationOptions{false, getSocketAddressCacheSize()}
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.
added a tmp variable for this.
Address::InstanceConstSharedPtr | ||
maybeGetDstAddressFromHeader(const cmsghdr& cmsg, uint32_t self_port, os_fd_t fd, bool v6only); | ||
|
||
std::unique_ptr<AddressInstanceLRUCache> recent_received_addresses_; |
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.
Brief comment, in particular explaining when it can be null.
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.
done
|
||
private: | ||
Address::InstanceConstSharedPtr | ||
maybeGetDstAddressFromHeader(const cmsghdr& cmsg, uint32_t self_port, os_fd_t fd, bool v6only); |
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.
Brief comment please. In particular, explain if the return value can be null and if so why, and what the "maybe" implies.
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.
done
quic_address, std::make_unique<Address::InstanceConstSharedPtr>(new_address)); | ||
return new_address; | ||
} | ||
return *it->second; |
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.
nit: since this is the smaller codepath, let's inver the conditional and reduce the nesting a tad:
if (it != recent_received_addresses_->end()) {
return *it->second;
}
Address::InstanceConstSharedPtr new_address =
Address::addressFromSockAddrOrDie(ss, sizeof(sockaddr_in6), fd, v6only);
recent_received_addresses_->Insert(
quic_address, std::make_unique<Address::InstanceConstSharedPtr>(new_address));
return new_address;
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.
done
quic_address, std::make_unique<Address::InstanceConstSharedPtr>(new_address)); | ||
return new_address; | ||
} | ||
return *it->second; |
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.
ditto
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.
done
} else { | ||
output.msg_[i].peer_address_ = Address::addressFromSockAddrOrDie( | ||
raw_addresses[i], hdr.msg_namelen, fd_, socket_v6only_ || !udp_read_normalize_addresses_); | ||
} |
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.
Hm. It seems like we have 4 places where we're doing basically the same thing of looking up an address in the cache and possible creating a new one. Can we share this code somehow, perhaps with a helper method?
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.
Moved them into a member function.
@@ -477,7 +533,7 @@ IoHandlePtr IoSocketHandleImpl::accept(struct sockaddr* addr, socklen_t* addrlen | |||
return nullptr; | |||
} | |||
return SocketInterfaceImpl::makePlatformSpecificSocket(result.return_value_, socket_v6only_, | |||
domain_); | |||
domain_, {}); |
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 does this do something different from the similar call below? Perhaps we should have a comment?
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.
accept() is only used by TCP socket which doesn't use the address cache. Whereas dup() can be used by both TCP and UDP, so copy over the same creation option.
@@ -172,10 +172,10 @@ void EnvoyQuicClientConnection::probeWithNewPort(const quic::QuicSocketAddress& | |||
Network::Address::InstanceConstSharedPtr new_local_address; | |||
if (current_local_address->ip()->version() == Network::Address::IpVersion::v4) { | |||
new_local_address = std::make_shared<Network::Address::Ipv4Instance>( | |||
current_local_address->ip()->addressAsString()); | |||
current_local_address->ip()->addressAsString(), ¤t_local_address->socketInterface()); |
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 not sure that I understand this change. Can you say more about it? I can't quite plumb it all through in my mind.
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 is not quite relevant to the rest of the PR. The Ipv4Instance has a handle to a socket interface extension which will be used to create a specific IoHandle instance. If not passed in, the global default extension will be use.
Here we are creating an alternative socket for this connection. The alternative socket should use the same socket interface extension to create IoHandle. Since the address cache is specific to IoSocketHandleImpl, we want to make sure the same extension is used across the connection life time.
It's hard to benchmark recvmsg and recvmmsg calls. I added unit test and integration test to ensure we don't create different address objects in each read. |
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
/wait |
/retest |
Signed-off-by: Dan Zhang <danzh@google.com>
/retest |
The Envoy/Publish and verify CI failure seems unrelated. |
/assign @ggreenway for a cross-company reviewer |
neither of for, a, cross-company, reviewer can be assigned to this issue. |
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.
/wait
Signed-off-by: Greg Greenway <ggreenway@apple.com>
/wait |
Signed-off-by: Dan Zhang <danzh@google.com>
Signed-off-by: Dan Zhang <danzh@google.com>
…voyproxy#34649) Maintain a LRU cache of Address::Instance objects in IoSocketHandleImpl to keep track of source and destination addresses received in recvmsg and recvmmsg. This cache is added mainly used for UDP read. This is an alternative to envoyproxy#33917 to address the issue that each recvmsg/recvmmsg will create a new instance of the source/destination addresses (which does stringification upon instantiation) even though they have been received before. The cache can be initialized during IoSocketHandleImpl construction based on a new param max_addresses_cache_size_ in SocketCreationOptions. This change only enables EnvoyQuicClientConnection to use a SocketCreationOptions with size 4 to create the underlying IoSocketHandleImpl. There is no change to the server side recvmsg and recvmmsg calls as the significant CPU cost on server side hasn't been observed. There is no cache support in non-IoSocketHandleImpl-based implementations. Signed-off-by: Dan Zhang <danzh@google.com> Signed-off-by: Fernando Cainelli <fernando.cainelli-external@getyourguide.com>
…voyproxy#34649) Maintain a LRU cache of Address::Instance objects in IoSocketHandleImpl to keep track of source and destination addresses received in recvmsg and recvmmsg. This cache is added mainly used for UDP read. This is an alternative to envoyproxy#33917 to address the issue that each recvmsg/recvmmsg will create a new instance of the source/destination addresses (which does stringification upon instantiation) even though they have been received before. The cache can be initialized during IoSocketHandleImpl construction based on a new param max_addresses_cache_size_ in SocketCreationOptions. This change only enables EnvoyQuicClientConnection to use a SocketCreationOptions with size 4 to create the underlying IoSocketHandleImpl. There is no change to the server side recvmsg and recvmmsg calls as the significant CPU cost on server side hasn't been observed. There is no cache support in non-IoSocketHandleImpl-based implementations. Signed-off-by: Dan Zhang <danzh@google.com> Signed-off-by: Fernando Cainelli <fernando.cainelli-external@getyourguide.com>
…voyproxy#34649) Maintain a LRU cache of Address::Instance objects in IoSocketHandleImpl to keep track of source and destination addresses received in recvmsg and recvmmsg. This cache is added mainly used for UDP read. This is an alternative to envoyproxy#33917 to address the issue that each recvmsg/recvmmsg will create a new instance of the source/destination addresses (which does stringification upon instantiation) even though they have been received before. The cache can be initialized during IoSocketHandleImpl construction based on a new param max_addresses_cache_size_ in SocketCreationOptions. This change only enables EnvoyQuicClientConnection to use a SocketCreationOptions with size 4 to create the underlying IoSocketHandleImpl. There is no change to the server side recvmsg and recvmmsg calls as the significant CPU cost on server side hasn't been observed. There is no cache support in non-IoSocketHandleImpl-based implementations. Signed-off-by: Dan Zhang <danzh@google.com> Signed-off-by: Fernando Cainelli <fernando.cainelli-external@getyourguide.com>
…voyproxy#34649) Maintain a LRU cache of Address::Instance objects in IoSocketHandleImpl to keep track of source and destination addresses received in recvmsg and recvmmsg. This cache is added mainly used for UDP read. This is an alternative to envoyproxy#33917 to address the issue that each recvmsg/recvmmsg will create a new instance of the source/destination addresses (which does stringification upon instantiation) even though they have been received before. The cache can be initialized during IoSocketHandleImpl construction based on a new param max_addresses_cache_size_ in SocketCreationOptions. This change only enables EnvoyQuicClientConnection to use a SocketCreationOptions with size 4 to create the underlying IoSocketHandleImpl. There is no change to the server side recvmsg and recvmmsg calls as the significant CPU cost on server side hasn't been observed. There is no cache support in non-IoSocketHandleImpl-based implementations. Signed-off-by: Dan Zhang <danzh@google.com> Signed-off-by: Fernando Cainelli <fernando.cainelli-external@getyourguide.com>
Commit Message: Maintain a LRU cache of Address::Instance objects in IoSocketHandleImpl to keep track of source and destination addresses received in recvmsg and recvmmsg. This cache is added mainly used for UDP read. This is an alternative to #33917 to address the issue that each recvmsg/recvmmsg will create a new instance of the source/destination addresses (which does stringification upon instantiation) even though they have been received before.
The cache can be initialized during IoSocketHandleImpl construction based on a new param
max_addresses_cache_size_
in SocketCreationOptions. This change only enables EnvoyQuicClientConnection to use a SocketCreationOptions with size 4 to create the underlying IoSocketHandleImpl. There is no change to the server side recvmsg and recvmmsg calls as the significant CPU cost on server side hasn't been observed.There is no cache support in non-IoSocketHandleImpl-based implementations.
Additional Description: Added QUICHE dependency to IoSocketHandleImpl to use data structures like
QuicLRUCache
andQuicSocketAddress
. Refactoredsource/common/http:utility_lib
to break the circular dependency as needed.Risk Level: low, only affect quic upstream
Testing: added new unit and integration tests
Docs Changes: N/A
Release Notes: yes
Platform Specific Features: N/A
Runtime guard: envoy.reloadable_features.quic_upstream_socket_use_address_cache_for_read