[PATCH 6 of 6] Conditional rearm of write timeouts
Maxim Dounin
mdounin at mdounin.ru
Fri May 2 00:43:09 UTC 2025
# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1746135925 -10800
# Fri May 02 00:45:25 2025 +0300
# Node ID 0aedbaa3cce2f488f376475d344de571af12ab7d
# Parent af60c59087e6721c8f24fae557a98617e7457823
Conditional rearm of write timeouts.
When the network memory limit is hit on Linux, it is possible that
connections are reported as writable, yet writev() / sendfile() returns
no progress and EAGAIN. This results in write timeouts being ineffective,
since they are rearmed on each write event.
In particular, such behaviour can be easily reproduced with SO_SNDBUF
explicitly set (via "listen ... sndbuf=...") and with poll or select
event methods. Before kernel 6.0 and 5.19.2, this also can be easily
reproduced with epoll (849b425cd091e "tcp: fix possible freeze in tx
path under memory pressure").
With this change, write timeouts are only rearmed if some progress is
made, ensuring that timeouts work properly in such situations.
diff --git a/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c
--- a/src/event/ngx_event_pipe.c
+++ b/src/event/ngx_event_pipe.c
@@ -22,10 +22,13 @@ static ngx_int_t ngx_event_pipe_drain_ch
ngx_int_t
ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write)
{
+ off_t sent;
ngx_int_t rc;
ngx_uint_t flags;
ngx_event_t *rev, *wev;
+ sent = p->downstream->sent;
+
for ( ;; ) {
if (do_write) {
p->log->action = "sending to client";
@@ -88,7 +91,9 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_
if (!wev->delayed) {
if (wev->active && !wev->ready) {
- ngx_add_timer(wev, p->send_timeout);
+ if (p->downstream->sent != sent || !wev->timer_set) {
+ ngx_add_timer(wev, p->send_timeout);
+ }
} else if (wev->timer_set) {
ngx_del_timer(wev);
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -2855,6 +2855,7 @@ ngx_http_set_write_handler(ngx_http_requ
static void
ngx_http_writer(ngx_http_request_t *r)
{
+ off_t sent;
ngx_int_t rc;
ngx_event_t *wev;
ngx_connection_t *c;
@@ -2892,6 +2893,8 @@ ngx_http_writer(ngx_http_request_t *r)
return;
}
+ sent = c->sent;
+
rc = ngx_http_output_filter(r, NULL);
ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
@@ -2905,7 +2908,7 @@ ngx_http_writer(ngx_http_request_t *r)
if (r->buffered || r->postponed || (r == r->main && c->buffered)) {
- if (!wev->delayed) {
+ if (!wev->delayed && (c->sent != sent || !wev->timer_set)) {
ngx_add_timer(wev, clcf->send_timeout);
}
diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -2103,6 +2103,7 @@ static void
ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u,
ngx_uint_t do_write)
{
+ off_t sent;
ngx_int_t rc;
ngx_connection_t *c;
@@ -2120,6 +2121,12 @@ ngx_http_upstream_send_request(ngx_http_
return;
}
+ sent = c->sent;
+
+ if (!u->request_sent) {
+ sent = -1;
+ }
+
c->log->action = "sending request to upstream";
rc = ngx_http_upstream_send_request_body(r, u, do_write);
@@ -2136,7 +2143,9 @@ ngx_http_upstream_send_request(ngx_http_
if (rc == NGX_AGAIN) {
if (!c->write->ready || u->request_body_blocked) {
- ngx_add_timer(c->write, u->conf->send_timeout);
+ if (c->sent != sent || !c->write->timer_set) {
+ ngx_add_timer(c->write, u->conf->send_timeout);
+ }
} else if (c->write->timer_set) {
ngx_del_timer(c->write);
@@ -3512,6 +3521,7 @@ static void
ngx_http_upstream_process_upgraded(ngx_http_request_t *r,
ngx_uint_t from_upstream, ngx_uint_t do_write)
{
+ off_t dsent, usent;
size_t size;
ssize_t n;
ngx_buf_t *b;
@@ -3529,6 +3539,9 @@ ngx_http_upstream_process_upgraded(ngx_h
downstream = c;
upstream = u->peer.connection;
+ dsent = downstream->sent;
+ usent = upstream->sent;
+
if (downstream->write->timedout) {
c->timedout = 1;
ngx_connection_error(c, NGX_ETIMEDOUT, "client timed out");
@@ -3648,7 +3661,9 @@ ngx_http_upstream_process_upgraded(ngx_h
}
if (upstream->write->active && !upstream->write->ready) {
- ngx_add_timer(upstream->write, u->conf->send_timeout);
+ if (upstream->sent != usent || !upstream->write->timer_set) {
+ ngx_add_timer(upstream->write, u->conf->send_timeout);
+ }
} else if (upstream->write->timer_set) {
ngx_del_timer(upstream->write);
@@ -3693,7 +3708,9 @@ ngx_http_upstream_process_upgraded(ngx_h
}
if (downstream->write->active && !downstream->write->ready) {
- ngx_add_timer(downstream->write, clcf->send_timeout);
+ if (downstream->sent != dsent || !downstream->write->timer_set) {
+ ngx_add_timer(downstream->write, clcf->send_timeout);
+ }
} else if (downstream->write->timer_set) {
ngx_del_timer(downstream->write);
@@ -3755,6 +3772,7 @@ static void
ngx_http_upstream_process_non_buffered_request(ngx_http_request_t *r,
ngx_uint_t do_write)
{
+ off_t sent;
size_t size;
ssize_t n;
ngx_buf_t *b;
@@ -3768,6 +3786,8 @@ ngx_http_upstream_process_non_buffered_r
downstream = r->connection;
upstream = u->peer.connection;
+ sent = downstream->sent;
+
b = &u->buffer;
do_write = do_write || u->length == 0;
@@ -3857,7 +3877,9 @@ ngx_http_upstream_process_non_buffered_r
}
if (downstream->write->active && !downstream->write->ready) {
- ngx_add_timer(downstream->write, clcf->send_timeout);
+ if (downstream->sent != sent || !downstream->write->timer_set) {
+ ngx_add_timer(downstream->write, clcf->send_timeout);
+ }
} else if (downstream->write->timer_set) {
ngx_del_timer(downstream->write);
diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c
--- a/src/http/v2/ngx_http_v2.c
+++ b/src/http/v2/ngx_http_v2.c
@@ -503,6 +503,7 @@ ngx_int_t
ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c)
{
int tcp_nodelay;
+ off_t sent;
ngx_chain_t *cl;
ngx_event_t *wev;
ngx_connection_t *c;
@@ -537,6 +538,8 @@ ngx_http_v2_send_output_queue(ngx_http_v
out->blocked, out->length);
}
+ sent = c->sent;
+
cl = c->send_chain(c, cl, 0);
if (cl == NGX_CHAIN_ERROR) {
@@ -592,7 +595,10 @@ ngx_http_v2_send_output_queue(ngx_http_v
h2c->last_out = frame;
if (!wev->ready) {
- ngx_add_timer(wev, clcf->send_timeout);
+ if (c->sent != sent || !wev->timer_set) {
+ ngx_add_timer(wev, clcf->send_timeout);
+ }
+
return NGX_AGAIN;
}
diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -1084,9 +1084,10 @@ ngx_mail_send(ngx_event_t *wev)
again:
- cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
-
- ngx_add_timer(wev, cscf->timeout);
+ if (n > 0 || !wev->timer_set) {
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+ ngx_add_timer(wev, cscf->timeout);
+ }
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_mail_close_connection(c);
diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c
--- a/src/mail/ngx_mail_proxy_module.c
+++ b/src/mail/ngx_mail_proxy_module.c
@@ -1116,6 +1116,7 @@ static void
ngx_mail_proxy_handler(ngx_event_t *ev)
{
char *action, *recv_action, *send_action;
+ off_t sent;
size_t size;
ssize_t n;
ngx_buf_t *b;
@@ -1181,6 +1182,7 @@ ngx_mail_proxy_handler(ngx_event_t *ev)
}
do_write = ev->write ? 1 : 0;
+ sent = dst->sent;
ngx_log_debug3(NGX_LOG_DEBUG_MAIL, ev->log, 0,
"mail proxy handler: %ui, #%d > #%d",
@@ -1276,7 +1278,7 @@ ngx_mail_proxy_handler(ngx_event_t *ev)
return;
}
- if (c == s->connection) {
+ if (c == s->connection && (dst->sent != sent || !ev->write)) {
pcf = ngx_mail_get_module_srv_conf(s, ngx_mail_proxy_module);
ngx_add_timer(c->read, pcf->timeout);
}
diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c
--- a/src/stream/ngx_stream_proxy_module.c
+++ b/src/stream/ngx_stream_proxy_module.c
@@ -1555,7 +1555,7 @@ ngx_stream_proxy_process(ngx_stream_sess
ngx_uint_t do_write)
{
char *recv_action, *send_action;
- off_t *received, limit;
+ off_t *received, limit, sent;
size_t size, limit_rate;
ssize_t n;
ngx_buf_t *b;
@@ -1615,6 +1615,14 @@ ngx_stream_proxy_process(ngx_stream_sess
send_action = "proxying and sending to upstream";
}
+#if (NGX_SUPPRESS_WARN)
+ sent = 0;
+#endif
+
+ if (dst) {
+ sent = dst->sent;
+ }
+
for ( ;; ) {
if (do_write && dst) {
@@ -1758,7 +1766,9 @@ ngx_stream_proxy_process(ngx_stream_sess
}
if (!c->read->delayed && !pc->read->delayed) {
- ngx_add_timer(c->write, pscf->timeout);
+ if (dst->sent != sent || !c->write->timer_set) {
+ ngx_add_timer(c->write, pscf->timeout);
+ }
} else if (c->write->timer_set) {
ngx_del_timer(c->write);
diff --git a/src/stream/ngx_stream_return_module.c b/src/stream/ngx_stream_return_module.c
--- a/src/stream/ngx_stream_return_module.c
+++ b/src/stream/ngx_stream_return_module.c
@@ -133,6 +133,7 @@ ngx_stream_return_handler(ngx_stream_ses
static void
ngx_stream_return_write_handler(ngx_event_t *ev)
{
+ off_t sent;
ngx_connection_t *c;
ngx_stream_session_t *s;
ngx_stream_return_ctx_t *ctx;
@@ -146,6 +147,8 @@ ngx_stream_return_write_handler(ngx_even
return;
}
+ sent = c->sent;
+
ctx = ngx_stream_get_module_ctx(s, ngx_stream_return_module);
if (ngx_stream_top_filter(s, ctx->out, 1) == NGX_ERROR) {
@@ -167,7 +170,9 @@ ngx_stream_return_write_handler(ngx_even
return;
}
- ngx_add_timer(ev, 5000);
+ if (c->sent != sent || !ev->timer_set) {
+ ngx_add_timer(ev, 5000);
+ }
}
More information about the nginx
mailing list