Limit the outgoing UDP send queue size

If the operating system UDP queue gets full and the outgoing UDP sending
starts to be delayed, BIND 9 could exhibit memory spikes as it tries to
enqueue all the outgoing UDP messages.  As those are not going to be
delivered anyway (as we argued when we stopped enlarging the operating
system send and receive buffers), try to send the UDP messages directly
using `uv_udp_try_send()` and if that fails, drop the outgoing UDP
message.

(cherry picked from commit b576c4c977)
This commit is contained in:
Ondřej Surý 2024-09-16 09:10:36 +02:00 committed by Ondřej Surý
parent 5c51e044c4
commit 3012a97d58
3 changed files with 48 additions and 4 deletions

View file

@ -60,6 +60,7 @@
*/
#define ISC_NETMGR_UDP_RECVBUF_SIZE UINT16_MAX
#endif
#define ISC_NETMGR_UDP_SENDBUF_SIZE UINT16_MAX
/*
* The TCP send and receive buffers can fit one maximum sized DNS message plus

View file

@ -19,6 +19,7 @@
#include <isc/buffer.h>
#include <isc/condition.h>
#include <isc/errno.h>
#include <isc/log.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/netmgr.h>
@ -27,6 +28,7 @@
#include <isc/region.h>
#include <isc/result.h>
#include <isc/sockaddr.h>
#include <isc/stdtime.h>
#include <isc/thread.h>
#include <isc/util.h>
@ -809,6 +811,21 @@ udp_send_cb(uv_udp_send_t *req, int status) {
isc__nm_sendcb(sock, uvreq, result, false);
}
static _Atomic(isc_stdtime_t) last_udpsends_log = 0;
static bool
can_log_udp_sends(void) {
isc_stdtime_t now;
isc_stdtime_get(&now);
isc_stdtime_t last = atomic_exchange_relaxed(&last_udpsends_log, now);
if (now != last) {
return (true);
}
return (false);
}
/*
* udp_send_direct sends buf to a peer on a socket. Sock has to be in
* the same thread as the callee.
@ -840,10 +857,35 @@ udp_send_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req,
}
#endif
r = uv_udp_send(&req->uv_req.udp_send, &sock->uv_handle.udp,
&req->uvbuf, 1, sa, udp_send_cb);
if (r < 0) {
return (isc__nm_uverr2result(r));
if (uv_udp_get_send_queue_size(&sock->uv_handle.udp) >
ISC_NETMGR_UDP_SENDBUF_SIZE)
{
/*
* The kernel UDP send queue is full, try sending the UDP
* response synchronously instead of just failing.
*/
r = uv_udp_try_send(&sock->uv_handle.udp, &req->uvbuf, 1, sa);
if (r < 0) {
if (can_log_udp_sends()) {
isc_log_write(isc_lctx, ISC_LOGCATEGORY_GENERAL,
ISC_LOGMODULE_NETMGR,
ISC_LOG_ERROR,
"Sending UDP messages failed: %s",
isc_result_totext(
isc__nm_uverr2result(r)));
}
return (isc__nm_uverr2result(r));
}
isc__nm_sendcb(sock, req, ISC_R_SUCCESS, true);
} else {
/* Send the message asynchronously */
r = uv_udp_send(&req->uv_req.udp_send, &sock->uv_handle.udp,
&req->uvbuf, 1, sa, udp_send_cb);
if (r < 0) {
return (isc__nm_uverr2result(r));
}
}
return (ISC_R_SUCCESS);

View file

@ -127,4 +127,5 @@ isc_uv_tcp_freebind(uv_tcp_t *handle, const struct sockaddr *addr,
#if UV_VERSION_HEX < UV_VERSION(1, 19, 0)
#define uv_stream_get_write_queue_size(stream) ((stream)->write_queue_size)
#define uv_udp_get_send_queue_size(handle) ((handle)->send_queue_size)
#endif /* UV_VERSION_HEX < UV_VERSION(1, 19, 0) */