[PATCH 6 of 7] Added the "send_min_rate" configuration directive

Maxim Dounin mdounin at mdounin.ru
Wed Jun 18 12:37:38 UTC 2025


# HG changeset patch
# User Maxim Dounin <mdounin at mdounin.ru>
# Date 1750204980 -10800
#      Wed Jun 18 03:03:00 2025 +0300
# Node ID 422bc6906b928c937e053a44c9854815223ff609
# Parent  4838a0361d54d7b3ed9de5c64baa5e9cd9439032
Added the "send_min_rate" configuration directive.

Specifies the minimum required data transmission rate to the client.
If the average transmission rate is below this value for send_timeout
period, the connection is closed.

The default value is 0, which implies that any data transfer results
in a new timeout (that is, the existing behaviour).

Algorithm used is as follows: the send_timeout timer is only rearmed
if average sending rate after the timer was last rearmed is above the
specified minimum.

Note that this directive might interfere with limit_rate if it is used:
minimum rates below ((limit_rate / 2 + limit_rate_after) / send_timeout)
might not work, due to delay timers used by limit_rate on c->write.
To avoid interference, using limit_rate_after at least as large as
(send_min_rate * send_timeout) is recommended.

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
@@ -17,6 +17,7 @@ static ngx_int_t ngx_event_pipe_write_to
 static ngx_int_t ngx_event_pipe_write_chain_to_temp_file(ngx_event_pipe_t *p);
 static ngx_inline void ngx_event_pipe_remove_shadow_links(ngx_buf_t *buf);
 static ngx_int_t ngx_event_pipe_drain_chains(ngx_event_pipe_t *p);
+static ngx_int_t ngx_event_pipe_min_rate(ngx_event_pipe_t *p, off_t sent);
 
 
 ngx_int_t
@@ -91,7 +92,7 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_
 
         if (!wev->delayed) {
             if (wev->active && !wev->ready) {
-                if (p->downstream->sent != sent || !wev->timer_set) {
+                if (ngx_event_pipe_min_rate(p, p->downstream->sent - sent)) {
                     ngx_add_timer(wev, p->send_timeout);
                 }
 
@@ -1170,3 +1171,42 @@ ngx_event_pipe_drain_chains(ngx_event_pi
         }
     }
 }
+
+
+static ngx_int_t
+ngx_event_pipe_min_rate(ngx_event_pipe_t *p, off_t sent)
+{
+    ngx_msec_t      now;
+    ngx_msec_int_t  ms;
+
+    if (p->send_min_rate == 0) {
+        return (sent > 0 || !p->downstream->write->timer_set);
+    }
+
+    now = ngx_current_msec;
+
+    if (p->send_min_last == 0 || !p->downstream->write->timer_set) {
+        p->send_min_last = now;
+        p->send_min_excess = 0;
+        return 1;
+    }
+
+    ms = (ngx_msec_int_t) (now - p->send_min_last);
+    ms = ngx_max(ms, 0);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_EVENT, p->log, 0,
+                   "pipe min rate: %O, %O, %M",
+                   sent, p->send_min_excess, ms);
+
+    if (p->send_min_excess + sent
+        > (off_t) p->send_min_rate * ms / 1000)
+    {
+        p->send_min_last = now;
+        p->send_min_excess = 0;
+        return 1;
+    }
+
+    p->send_min_excess += sent;
+
+    return 0;
+}
diff --git a/src/event/ngx_event_pipe.h b/src/event/ngx_event_pipe.h
--- a/src/event/ngx_event_pipe.h
+++ b/src/event/ngx_event_pipe.h
@@ -94,6 +94,10 @@ struct ngx_event_pipe_s {
     ngx_msec_t         limit_last;
     off_t              limit_excess;
 
+    size_t             send_min_rate;
+    ngx_msec_t         send_min_last;
+    off_t              send_min_excess;
+
     ngx_temp_file_t   *temp_file;
 
     /* STUB */ int     num;
diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h
--- a/src/http/ngx_http.h
+++ b/src/http/ngx_http.h
@@ -166,6 +166,9 @@ void ngx_http_block_reading(ngx_http_req
 void ngx_http_test_reading(ngx_http_request_t *r);
 
 
+void ngx_http_send_timeout(ngx_http_request_t *r, off_t sent);
+
+
 char *ngx_http_types_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
 char *ngx_http_merge_types(ngx_conf_t *cf, ngx_array_t **keys,
     ngx_hash_t *types_hash, ngx_array_t **prev_keys,
diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -472,6 +472,13 @@ static ngx_command_t  ngx_http_core_comm
       offsetof(ngx_http_core_loc_conf_t, send_timeout),
       NULL },
 
+    { ngx_string("send_min_rate"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_size_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_core_loc_conf_t, send_min_rate),
+      NULL },
+
     { ngx_string("send_lowat"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
       ngx_conf_set_size_slot,
@@ -3609,6 +3616,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t
     clcf->tcp_nopush = NGX_CONF_UNSET;
     clcf->tcp_nodelay = NGX_CONF_UNSET;
     clcf->send_timeout = NGX_CONF_UNSET_MSEC;
+    clcf->send_min_rate = NGX_CONF_UNSET_SIZE;
     clcf->send_lowat = NGX_CONF_UNSET_SIZE;
     clcf->postpone_output = NGX_CONF_UNSET_SIZE;
     clcf->limit_rate = NGX_CONF_UNSET_PTR;
@@ -3840,6 +3848,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t 
     ngx_conf_merge_value(conf->tcp_nodelay, prev->tcp_nodelay, 1);
 
     ngx_conf_merge_msec_value(conf->send_timeout, prev->send_timeout, 60000);
+    ngx_conf_merge_size_value(conf->send_min_rate, prev->send_min_rate, 0);
     ngx_conf_merge_size_value(conf->send_lowat, prev->send_lowat, 0);
     ngx_conf_merge_size_value(conf->postpone_output, prev->postpone_output,
                               1460);
diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -359,6 +359,7 @@ struct ngx_http_core_loc_conf_s {
     off_t         directio_alignment;      /* directio_alignment */
 
     size_t        client_body_buffer_size; /* client_body_buffer_size */
+    size_t        send_min_rate;           /* send_min_rate */
     size_t        send_lowat;              /* send_lowat */
     size_t        postpone_output;         /* postpone_output */
     size_t        sendfile_max_chunk;      /* sendfile_max_chunk */
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
@@ -39,6 +39,7 @@ static void ngx_http_terminate_handler(n
 static void ngx_http_finalize_connection(ngx_http_request_t *r);
 static ngx_int_t ngx_http_set_write_handler(ngx_http_request_t *r);
 static void ngx_http_writer(ngx_http_request_t *r);
+static ngx_int_t ngx_http_send_min_rate(ngx_http_request_t *r, off_t sent);
 static void ngx_http_request_finalizer(ngx_http_request_t *r);
 
 static void ngx_http_set_keepalive(ngx_http_request_t *r);
@@ -2835,11 +2836,11 @@ ngx_http_set_write_handler(ngx_http_requ
         return NGX_OK;
     }
 
+    if (!wev->delayed) {
+        ngx_http_send_timeout(r, 0);
+    }
+
     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-    if (!wev->delayed) {
-        ngx_add_timer(wev, clcf->send_timeout);
-    }
-
     if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
         ngx_http_close_request(r, 0);
         return NGX_ERROR;
@@ -2880,7 +2881,7 @@ ngx_http_writer(ngx_http_request_t *r)
                        "http writer delayed");
 
         if (!wev->delayed) {
-            ngx_add_timer(wev, clcf->send_timeout);
+            ngx_http_send_timeout(r, 0);
         }
 
         if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
@@ -2905,8 +2906,8 @@ ngx_http_writer(ngx_http_request_t *r)
 
     if (r->buffered || r->postponed || (r == r->main && c->buffered)) {
 
-        if (!wev->delayed && (c->sent != sent || !wev->timer_set)) {
-            ngx_add_timer(wev, clcf->send_timeout);
+        if (!wev->delayed) {
+            ngx_http_send_timeout(r, c->sent - sent);
         }
 
         if (ngx_handle_write_event(wev, clcf->send_lowat) != NGX_OK) {
@@ -2925,6 +2926,66 @@ ngx_http_writer(ngx_http_request_t *r)
 }
 
 
+void
+ngx_http_send_timeout(ngx_http_request_t *r, off_t sent)
+{
+    ngx_http_core_loc_conf_t  *clcf;
+
+    if (!ngx_http_send_min_rate(r, sent)) {
+        return;
+    }
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+    ngx_add_timer(r->connection->write, clcf->send_timeout);
+}
+
+
+static ngx_int_t
+ngx_http_send_min_rate(ngx_http_request_t *r, off_t sent)
+{
+    ngx_msec_t                 now;
+    ngx_msec_int_t             ms;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    r = r->main;
+    c = r->connection;
+
+    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
+
+    if (clcf->send_min_rate == 0) {
+        return (sent > 0 || !c->write->timer_set);
+    }
+
+    now = ngx_current_msec;
+
+    if (r->send_min_last == 0 || !c->write->timer_set) {
+        r->send_min_last = now;
+        r->send_min_excess = 0;
+        return 1;
+    }
+
+    ms = (ngx_msec_int_t) (now - r->send_min_last);
+    ms = ngx_max(ms, 0);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http min rate: %O, %O, %M",
+                   sent, r->send_min_excess, ms);
+
+    if (r->send_min_excess + sent
+        > (off_t) clcf->send_min_rate * ms / 1000)
+    {
+        r->send_min_last = now;
+        r->send_min_excess = 0;
+        return 1;
+    }
+
+    r->send_min_excess += sent;
+
+    return 0;
+}
+
+
 static void
 ngx_http_request_finalizer(ngx_http_request_t *r)
 {
diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -450,6 +450,9 @@ struct ngx_http_request_s {
     ngx_msec_t                        limit_last;
     off_t                             limit_excess;
 
+    ngx_msec_t                        send_min_last;
+    off_t                             send_min_excess;
+
     /* used to learn the Apache compatible response length without a header */
     size_t                            header_size;
 
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
@@ -3368,6 +3368,7 @@ ngx_http_upstream_send_response(ngx_http
 
     p->read_timeout = u->conf->read_timeout;
     p->send_timeout = clcf->send_timeout;
+    p->send_min_rate = clcf->send_min_rate;
     p->send_lowat = clcf->send_lowat;
 
     p->length = -1;
@@ -3707,9 +3708,7 @@ ngx_http_upstream_process_upgraded(ngx_h
     }
 
     if (downstream->write->active && !downstream->write->ready) {
-        if (downstream->sent != dsent || !downstream->write->timer_set) {
-            ngx_add_timer(downstream->write, clcf->send_timeout);
-        }
+        ngx_http_send_timeout(r, downstream->sent - dsent);
 
     } else if (downstream->write->timer_set) {
         ngx_del_timer(downstream->write);
@@ -3876,9 +3875,7 @@ ngx_http_upstream_process_non_buffered_r
     }
 
     if (downstream->write->active && !downstream->write->ready) {
-        if (downstream->sent != sent || !downstream->write->timer_set) {
-            ngx_add_timer(downstream->write, clcf->send_timeout);
-        }
+        ngx_http_send_timeout(r, downstream->sent - sent);
 
     } 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
@@ -51,6 +51,8 @@
 
 static void ngx_http_v2_read_handler(ngx_event_t *rev);
 static void ngx_http_v2_write_handler(ngx_event_t *wev);
+static ngx_int_t ngx_http_v2_send_min_rate(ngx_http_v2_connection_t *h2c,
+    off_t sent);
 static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c);
 static void ngx_http_v2_lingering_close(ngx_connection_t *c);
 static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev);
@@ -595,7 +597,7 @@ ngx_http_v2_send_output_queue(ngx_http_v
     h2c->last_out = frame;
 
     if (!wev->ready) {
-        if (c->sent != sent || !wev->timer_set) {
+        if (ngx_http_v2_send_min_rate(h2c, c->sent - sent)) {
             ngx_add_timer(wev, clcf->send_timeout);
         }
 
@@ -620,6 +622,52 @@ error:
 }
 
 
+static ngx_int_t
+ngx_http_v2_send_min_rate(ngx_http_v2_connection_t *h2c, off_t sent)
+{
+    ngx_msec_t                 now;
+    ngx_msec_int_t             ms;
+    ngx_connection_t          *c;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    c = h2c->connection;
+
+    clcf = ngx_http_get_module_loc_conf(h2c->http_connection->conf_ctx,
+                                        ngx_http_core_module);
+
+    if (clcf->send_min_rate == 0) {
+        return (sent > 0 || !c->write->timer_set);
+    }
+
+    now = ngx_current_msec;
+
+    if (h2c->send_min_last == 0 || !c->write->timer_set) {
+        h2c->send_min_last = now;
+        h2c->send_min_excess = 0;
+        return 1;
+    }
+
+    ms = (ngx_msec_int_t) (now - h2c->send_min_last);
+    ms = ngx_max(ms, 0);
+
+    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                   "http2 min rate: %O, %O, %M",
+                   sent, h2c->send_min_excess, ms);
+
+    if (h2c->send_min_excess + sent
+        > (off_t) clcf->send_min_rate * ms / 1000)
+    {
+        h2c->send_min_last = now;
+        h2c->send_min_excess = 0;
+        return 1;
+    }
+
+    h2c->send_min_excess += sent;
+
+    return 0;
+}
+
+
 static void
 ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c)
 {
diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h
--- a/src/http/v2/ngx_http_v2.h
+++ b/src/http/v2/ngx_http_v2.h
@@ -164,6 +164,9 @@ struct ngx_http_v2_connection_s {
 
     time_t                           lingering_time;
 
+    ngx_msec_t                       send_min_last;
+    off_t                            send_min_excess;
+
     unsigned                         settings_ack:1;
     unsigned                         table_update:1;
     unsigned                         blocked:1;



More information about the nginx-devel mailing list