-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
udp: limit number of reads per event loop #16180
Changes from 12 commits
a84505d
879cd6f
24ae38d
48a644f
b89f7fe
758c321
ded13a6
51c3404
f0949ea
0858725
001f228
efb5bef
09ba49d
fb6bb16
372b3e3
2151e2d
d9989e8
e0aae6f
dcda8e8
80e91f6
8cd5cda
45e3518
3be3b84
3d82197
077b6d3
33d1bd3
9928fb3
5700114
3ec57b7
7c0b9d4
c3f20a5
6622e2b
d8538ed
ee2db2d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,11 @@ import "envoy/config/core/v3/base.proto"; | |
import "envoy/config/core/v3/protocol.proto"; | ||
|
||
import "google/protobuf/duration.proto"; | ||
import "google/protobuf/wrappers.proto"; | ||
|
||
import "udpa/annotations/status.proto"; | ||
import "udpa/annotations/versioning.proto"; | ||
import "validate/validate.proto"; | ||
|
||
option java_package = "io.envoyproxy.envoy.config.listener.v3"; | ||
option java_outer_classname = "QuicConfigProto"; | ||
|
@@ -18,6 +20,7 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE; | |
// [#protodoc-title: QUIC listener config] | ||
|
||
// Configuration specific to the UDP QUIC listener. | ||
// [#next-free-field: 6] | ||
message QuicProtocolOptions { | ||
option (udpa.annotations.versioning).previous_message_type = | ||
"envoy.api.v2.listener.QuicProtocolOptions"; | ||
|
@@ -35,4 +38,9 @@ message QuicProtocolOptions { | |
// Runtime flag that controls whether the listener is enabled or not. If not specified, defaults | ||
// to enabled. | ||
core.v3.RuntimeFeatureFlag enabled = 4; | ||
|
||
// A multiplier to number of connections which is used to determine how many packets to read per event loop. A reasonable ratio should allow the listener to process enough payload but not starve TCP and other UDP sockets and also prevent long event loop duration. | ||
// The default value is 32. This means if there are N QUIC connections, the total number of packets to read in each READ event will be 32 * N. | ||
google.protobuf.UInt32Value packets_received_to_connection_count_ratio = 5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. optional: packets_to_read_per_connection ? We should document the upper bound here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
[(validate.rules).uint32 = {gte: 1}]; | ||
alyssawilk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -330,6 +330,11 @@ class UdpListenerCallbacks { | |
* Posts ``data`` to be delivered on this worker. | ||
*/ | ||
virtual void post(Network::UdpRecvData&& data) PURE; | ||
|
||
/** | ||
* An estimated number of packets this callback expects to process in current READ event. | ||
*/ | ||
virtual size_t numPacketsExpectedPerEventLoop() const PURE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is READ in all caps? also for naming is this the number of packets expected, or the number of packets the listener should be willing to read? Should we make it clear in comments this is TCP specific? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. READ doesn't need to be all caps. Fixed. For the naming confusion, what's the difference between "the number of packets expected" and "the number of packets the listener should be willing to read"? This interface provides a hint to UDP listener but eventually the listener will decide how many to read. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
READ doesn't need to be all caps. Fixed.
What's the difference between "the number of packets expected" and "the number of packets the listener should be willing to read"? This interface provides a hint to UDP listener but eventually the listener will decide how many to read.
UDP specific? This is in UdpListenerCallbacks after all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's expected vs allowed to be read. and yes, I meant UDP. UdpPackets maybe since tcp packets are a thing too? |
||
}; | ||
|
||
using UdpListenerCallbacksOptRef = absl::optional<std::reference_wrapper<UdpListenerCallbacks>>; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,7 +75,12 @@ void UdpListenerImpl::handleReadCallback() { | |
const Api::IoErrorPtr result = Utility::readPacketsFromSocket( | ||
socket_->ioHandle(), *socket_->addressProvider().localAddress(), *this, time_source_, | ||
config_.prefer_gro_, packets_dropped_); | ||
// TODO(mattklein123): Handle no error when we limit the number of packets read. | ||
if (result == nullptr) { | ||
// No error. The number of reads was limited by read rate. There are more packets to read. | ||
// Register READ for next event loop. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Register to read more in the next event loop? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
socket_->ioHandle().activateFileEvents(Event::FileReadyType::Read); | ||
return; | ||
} | ||
if (result->getErrorCode() != Api::IoError::IoErrorCode::Again) { | ||
// TODO(mattklein123): When rate limited logging is implemented log this at error level | ||
// on a periodic basis. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -576,10 +576,10 @@ void passPayloadToProcessor(uint64_t bytes_read, Buffer::InstancePtr buffer, | |
Api::IoCallUint64Result Utility::readFromSocket(IoHandle& handle, | ||
const Address::Instance& local_address, | ||
UdpPacketProcessor& udp_packet_processor, | ||
MonotonicTime receive_time, bool prefer_gro, | ||
MonotonicTime receive_time, bool use_gro, | ||
uint32_t* packets_dropped) { | ||
|
||
if (prefer_gro && handle.supportsUdpGro()) { | ||
if (use_gro) { | ||
Buffer::InstancePtr buffer = std::make_unique<Buffer::OwnedImpl>(); | ||
IoHandle::RecvMsgOutput output(1, packets_dropped); | ||
|
||
|
@@ -696,11 +696,22 @@ Api::IoErrorPtr Utility::readPacketsFromSocket(IoHandle& handle, | |
UdpPacketProcessor& udp_packet_processor, | ||
TimeSource& time_source, bool prefer_gro, | ||
uint32_t& packets_dropped) { | ||
// Make sure at not too many packets will be read in following loop, but at least it will read | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this comment needs a rephrase. Read at least one time, and attempt to read numPacketsExpectedPerEventLoop() packets unless this goes over MAX_NUM_PACKETS_PER_EVENT_LOOP There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
// once. | ||
size_t num_packets_to_read = std::min(MAX_NUM_PACKETS_PER_EVENT_LOOP, | ||
udp_packet_processor.numPacketsExpectedPerEventLoop()); | ||
const bool use_gro = prefer_gro && handle.supportsUdpGro(); | ||
size_t num_reads = | ||
use_gro ? (num_packets_to_read / NUM_DATAGRAMS_PER_GRO_RECEIVE) | ||
: (handle.supportsMmsg() ? (num_packets_to_read / NUM_DATAGRAMS_PER_MMSG_RECEIVE) | ||
: num_packets_to_read); | ||
// Make sure at least read once. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Make sure to read at least once There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
num_reads = std::max(1ul, num_reads); | ||
do { | ||
const uint32_t old_packets_dropped = packets_dropped; | ||
const MonotonicTime receive_time = time_source.monotonicTime(); | ||
Api::IoCallUint64Result result = Utility::readFromSocket( | ||
handle, local_address, udp_packet_processor, receive_time, prefer_gro, &packets_dropped); | ||
handle, local_address, udp_packet_processor, receive_time, use_gro, &packets_dropped); | ||
|
||
if (!result.ok()) { | ||
// No more to read or encountered a system error. | ||
|
@@ -723,6 +734,10 @@ Api::IoErrorPtr Utility::readPacketsFromSocket(IoHandle& handle, | |
delta); | ||
udp_packet_processor.onDatagramsDropped(delta); | ||
} | ||
--num_reads; | ||
if (num_reads == 0) { | ||
return std::move(result.err_); | ||
} | ||
} while (true); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,11 +61,17 @@ class UdpPacketProcessor { | |
* the size of datagrams received, they will be dropped. | ||
*/ | ||
virtual uint64_t maxDatagramSize() const PURE; | ||
|
||
/** | ||
* An estimated number of packets to read in each READ event. | ||
*/ | ||
virtual size_t numPacketsExpectedPerEventLoop() const PURE; | ||
}; | ||
|
||
static const uint64_t DEFAULT_UDP_MAX_DATAGRAM_SIZE = 1500; | ||
static const uint64_t NUM_DATAGRAMS_PER_GRO_RECEIVE = 16; | ||
static const uint64_t NUM_DATAGRAMS_PER_MMSG_RECEIVE = 16; | ||
static const uint64_t MAX_NUM_PACKETS_PER_EVENT_LOOP = 6000; | ||
alyssawilk marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Wrapper which resolves UDP socket proto config with defaults. | ||
|
@@ -362,18 +368,20 @@ class Utility { | |
static Api::IoCallUint64Result readFromSocket(IoHandle& handle, | ||
const Address::Instance& local_address, | ||
UdpPacketProcessor& udp_packet_processor, | ||
MonotonicTime receive_time, bool prefer_gro, | ||
MonotonicTime receive_time, bool use_gro, | ||
uint32_t* packets_dropped); | ||
|
||
/** | ||
* Read available packets from a given UDP socket and pass the packet to a given | ||
* UdpPacketProcessor. | ||
* Read some packets from a given UDP socket and pass the packet to a given | ||
* UdpPacketProcessor. Only read no more than MAX_NUM_PACKETS_PER_EVENT_LOOP packets. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Read no more than ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
* @param handle is the UDP socket to read from. | ||
* @param local_address is the socket's local address used to populate port. | ||
* @param udp_packet_processor is the callback to receive the packets. | ||
* @param time_source is the time source used to generate the time stamp of the received packets. | ||
* @param prefer_gro supplies whether to use GRO if the OS supports it. | ||
* @param packets_dropped is the output parameter for number of packets dropped in kernel. | ||
* Return the io error encountered or nullptr if no io error but read stopped | ||
* because of MAX_NUM_PACKETS_PER_EVENT_LOOP. | ||
* | ||
* TODO(mattklein123): Allow the number of packets read to be limited for fairness. Currently | ||
* this function will always return an error, even if EAGAIN. In the future | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,25 +25,29 @@ ActiveQuicListener::ActiveQuicListener( | |
uint32_t worker_index, uint32_t concurrency, Event::Dispatcher& dispatcher, | ||
Network::UdpConnectionHandler& parent, Network::ListenerConfig& listener_config, | ||
const quic::QuicConfig& quic_config, Network::Socket::OptionsSharedPtr options, | ||
bool kernel_worker_routing, const envoy::config::core::v3::RuntimeFeatureFlag& enabled) | ||
bool kernel_worker_routing, const envoy::config::core::v3::RuntimeFeatureFlag& enabled, | ||
uint32_t packets_received_to_connection_count_ratio) | ||
: ActiveQuicListener(worker_index, concurrency, dispatcher, parent, | ||
listener_config.listenSocketFactory().getListenSocket(), listener_config, | ||
quic_config, std::move(options), kernel_worker_routing, enabled) {} | ||
quic_config, std::move(options), kernel_worker_routing, enabled, | ||
packets_received_to_connection_count_ratio) {} | ||
|
||
ActiveQuicListener::ActiveQuicListener( | ||
uint32_t worker_index, uint32_t concurrency, Event::Dispatcher& dispatcher, | ||
Network::UdpConnectionHandler& parent, Network::SocketSharedPtr listen_socket, | ||
Network::ListenerConfig& listener_config, const quic::QuicConfig& quic_config, | ||
Network::Socket::OptionsSharedPtr options, bool kernel_worker_routing, | ||
const envoy::config::core::v3::RuntimeFeatureFlag& enabled) | ||
const envoy::config::core::v3::RuntimeFeatureFlag& enabled, | ||
uint32_t packets_received_to_connection_count_ratio) | ||
: Server::ActiveUdpListenerBase( | ||
worker_index, concurrency, parent, *listen_socket, | ||
dispatcher.createUdpListener( | ||
listen_socket, *this, | ||
listener_config.udpListenerConfig()->config().downstream_socket_config()), | ||
&listener_config), | ||
dispatcher_(dispatcher), version_manager_(quic::CurrentSupportedVersions()), | ||
kernel_worker_routing_(kernel_worker_routing) { | ||
kernel_worker_routing_(kernel_worker_routing), | ||
packets_received_to_connection_count_ratio_(packets_received_to_connection_count_ratio) { | ||
// This flag fix a QUICHE issue which may crash Envoy during connection close. | ||
SetQuicReloadableFlag(quic_single_ack_in_packet2, true); | ||
// Do not include 32-byte per-entry overhead while counting header size. | ||
|
@@ -214,9 +218,16 @@ uint32_t ActiveQuicListener::destination(const Network::UdpRecvData& data) const | |
return connection_id_snippet % concurrency_; | ||
} | ||
|
||
size_t ActiveQuicListener::numPacketsExpectedPerEventLoop() const { | ||
// Expect each session to read 32 packets per READ event. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the configured number may not be 32.? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fixed |
||
return quic_dispatcher_->NumSessions() * packets_received_to_connection_count_ratio_; | ||
} | ||
|
||
ActiveQuicListenerFactory::ActiveQuicListenerFactory( | ||
const envoy::config::listener::v3::QuicProtocolOptions& config, uint32_t concurrency) | ||
: concurrency_(concurrency), enabled_(config.enabled()) { | ||
: concurrency_(concurrency), enabled_(config.enabled()), | ||
packets_received_to_connection_count_ratio_( | ||
PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, packets_received_to_connection_count_ratio, 32)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should 32 be a const default defined somewhere? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added MAX_NUM_PACKETS_PER_EVENT_LOOP |
||
uint64_t idle_network_timeout_ms = | ||
config.has_idle_timeout() ? DurationUtil::durationToMilliseconds(config.idle_timeout()) | ||
: 300000; | ||
|
@@ -299,9 +310,9 @@ Network::ConnectionHandler::ActiveUdpListenerPtr ActiveQuicListenerFactory::crea | |
} | ||
#endif | ||
|
||
return std::make_unique<ActiveQuicListener>(worker_index, concurrency_, disptacher, parent, | ||
config, quic_config_, std::move(options), | ||
kernel_worker_routing, enabled_); | ||
return std::make_unique<ActiveQuicListener>( | ||
worker_index, concurrency_, disptacher, parent, config, quic_config_, std::move(options), | ||
kernel_worker_routing, enabled_, packets_received_to_connection_count_ratio_); | ||
} // namespace Quic | ||
|
||
} // namespace Quic | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,6 +53,10 @@ class EnvoyQuicClientConnection : public quic::QuicConnection, | |
void onDatagramsDropped(uint32_t) override { | ||
// TODO(mattklein123): Emit a stat for this. | ||
} | ||
size_t numPacketsExpectedPerEventLoop() const override { | ||
// Use ~32k to read the same amount as a TCP connection. | ||
return 32u; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, let's define this somewhere, comment there, and use in both places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
} | ||
|
||
// Register file event and apply socket options. | ||
void setUpConnectionSocket(); | ||
|
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: line length
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