# HG changeset patch # User Maxim Dounin <mdou...@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); + } }