[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