# 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);
+    }
 }
 
 

Reply via email to