Skip to content

Commit 72efb40

Browse files
committed
Changed limit_rate algorithm to leaky bucket.
The bucket size (burst size) is set to the size expected to be sent in 1 second (that is, the rate configured), so the first operation will be able to send corresponding number of bytes, matching the previous behaviour. To ensure that inaccurate timers won't affect rate limiting accuracy, delays are set to trigger next sending when the bucket is half-empty. The limit_rate_after directive makes the bucket size larger, allowing for larger traffic bursts (it won't affect delays though). This is mostly equivalent to the previous behaviour in simple cases, like downloading a static file by a fast client, but expected to work better in complex cases, such as when actual response traffic might stop for a while. Similar changes are made to proxy_limit_rate (and friends) in the http module, as well as proxy_upload_rate and proxy_download_rate in the stream module. Immediate benefits include more accurate limiting, notably at rates which resulted in 1 or 2 millisecond delays after sending (and very inaccurate limiting) with the old algorithm. Further, there will be less unneeded delays, notably no delays at all in most cases as long as the client is not using allowed bandwidth. Note that due to the new algorithm there are minor changes in the limiting, mostly visible with very small limits: typically 1.5x of the limit will be sent in the first second (one immediately, and half after 0.5 seconds delay), and additional data will be sent once per 0.5 seconds (instead of once per second previously). Some tests needs to be adapted for this. Note that r->limit_last is not initialized, and will be set to an actual value only after first sending. As a result, "ms" can be large during initial sending, and to ensure that there will be no signed integer overflows, calculations of "excess" are performed in (uint64_t) (and the result is type casted into (off_t) to clarify that the code intentionally relies on implementation-defined behaviour, similarly to how we handle calculations of "ms").
1 parent 9ab25bc commit 72efb40

File tree

7 files changed

+98
-42
lines changed

7 files changed

+98
-42
lines changed

src/event/ngx_event_pipe.c

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,13 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write)
108108
static ngx_int_t
109109
ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
110110
{
111-
off_t limit;
112-
ssize_t n, size;
113-
ngx_int_t rc;
114-
ngx_buf_t *b;
115-
ngx_msec_t delay;
116-
ngx_chain_t *chain, *cl, *ln;
111+
off_t limit, excess;
112+
ssize_t n, size, sent;
113+
ngx_int_t rc;
114+
ngx_buf_t *b;
115+
ngx_msec_t delay;
116+
ngx_chain_t *chain, *cl, *ln;
117+
ngx_msec_int_t ms;
117118

118119
if (p->upstream_eof || p->upstream_error || p->upstream_done
119120
|| p->upstream == NULL)
@@ -145,6 +146,8 @@ ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
145146
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, p->log, 0,
146147
"pipe read upstream: %d", p->upstream->read->ready);
147148

149+
excess = 0;
150+
148151
for ( ;; ) {
149152

150153
if (p->upstream_eof || p->upstream_error || p->upstream_done) {
@@ -212,12 +215,19 @@ ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
212215
break;
213216
}
214217

215-
limit = (off_t) p->limit_rate * (ngx_time() - p->start_sec + 1)
216-
- p->read_length;
218+
ms = (ngx_msec_int_t) (ngx_current_msec - p->limit_last);
219+
ms = ngx_max(ms, 0);
220+
221+
excess = (off_t) (p->limit_excess
222+
- (uint64_t) p->limit_rate * ms / 1000);
223+
excess = ngx_max(excess, 0);
224+
225+
limit = (off_t) p->limit_rate - excess;
217226

218227
if (limit <= 0) {
219228
p->upstream->read->delayed = 1;
220-
delay = (ngx_msec_t) (- limit * 1000 / p->limit_rate + 1);
229+
excess -= (off_t) p->limit_rate / 2;
230+
delay = (ngx_msec_t) (excess * 1000 / p->limit_rate + 1);
221231
ngx_add_timer(p->upstream->read, delay);
222232
break;
223233
}
@@ -345,8 +355,7 @@ ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
345355
}
346356
}
347357

348-
delay = p->limit_rate ? (ngx_msec_t) n * 1000 / p->limit_rate : 0;
349-
358+
sent = n;
350359
p->read_length += n;
351360
cl = chain;
352361
p->free_raw_bufs = NULL;
@@ -384,10 +393,22 @@ ngx_event_pipe_read_upstream(ngx_event_pipe_t *p)
384393
p->free_raw_bufs = cl;
385394
}
386395

387-
if (delay > 0) {
388-
p->upstream->read->delayed = 1;
389-
ngx_add_timer(p->upstream->read, delay);
390-
break;
396+
if (p->limit_rate) {
397+
excess += sent;
398+
399+
p->limit_last = ngx_current_msec;
400+
p->limit_excess = excess;
401+
402+
excess -= (off_t) p->limit_rate / 2;
403+
excess = ngx_max(excess, 0);
404+
405+
delay = (ngx_msec_t) (excess * 1000 / p->limit_rate);
406+
407+
if (delay > 0) {
408+
p->upstream->read->delayed = 1;
409+
ngx_add_timer(p->upstream->read, delay);
410+
break;
411+
}
391412
}
392413
}
393414

src/event/ngx_event_pipe.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ struct ngx_event_pipe_s {
9191
ngx_buf_t *buf_to_file;
9292

9393
size_t limit_rate;
94-
time_t start_sec;
94+
ngx_msec_t limit_last;
95+
off_t limit_excess;
9596

9697
ngx_temp_file_t *temp_file;
9798

src/http/ngx_http_request.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,9 @@ struct ngx_http_request_s {
448448
size_t limit_rate;
449449
size_t limit_rate_after;
450450

451+
ngx_msec_t limit_last;
452+
off_t limit_excess;
453+
451454
/* used to learn the Apache compatible response length without a header */
452455
size_t header_size;
453456

src/http/ngx_http_upstream.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3271,7 +3271,6 @@ ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
32713271
p->pool = r->pool;
32723272
p->log = c->log;
32733273
p->limit_rate = u->conf->limit_rate;
3274-
p->start_sec = ngx_time();
32753274

32763275
p->cacheable = u->cacheable || u->store;
32773276

src/http/ngx_http_write_filter_module.c

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ ngx_module_t ngx_http_write_filter_module = {
4747
ngx_int_t
4848
ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
4949
{
50-
off_t size, sent, nsent, limit;
50+
off_t size, sent, excess, limit;
5151
ngx_uint_t last, flush, sync;
5252
ngx_msec_t delay;
5353
ngx_chain_t *cl, *ln, **ll, *chain;
54+
ngx_msec_int_t ms;
5455
ngx_connection_t *c;
5556
ngx_http_core_loc_conf_t *clcf;
5657

@@ -66,6 +67,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
6667
last = 0;
6768
ll = &r->out;
6869

70+
#if (NGX_SUPPRESS_WARN)
71+
excess = 0;
72+
#endif
73+
6974
/* find the size, the flush point and the last link of the saved chain */
7075

7176
for (cl = r->out; cl; cl = cl->next) {
@@ -268,12 +273,19 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
268273
r->limit_rate_after_set = 1;
269274
}
270275

271-
limit = (off_t) r->limit_rate * (ngx_time() - r->start_sec + 1)
272-
- (c->sent - r->limit_rate_after);
276+
ms = (ngx_msec_int_t) (ngx_current_msec - r->limit_last);
277+
ms = ngx_max(ms, 0);
278+
279+
excess = (off_t) (r->limit_excess
280+
- (uint64_t) r->limit_rate * ms / 1000);
281+
excess = ngx_max(excess, 0);
282+
283+
limit = (off_t) r->limit_rate + (off_t) r->limit_rate_after - excess;
273284

274285
if (limit <= 0) {
275286
c->write->delayed = 1;
276-
delay = (ngx_msec_t) (- limit * 1000 / r->limit_rate + 1);
287+
excess -= (off_t) r->limit_rate_after + (off_t) r->limit_rate / 2;
288+
delay = (ngx_msec_t) (excess * 1000 / r->limit_rate + 1);
277289
ngx_add_timer(c->write, delay);
278290

279291
c->buffered |= NGX_HTTP_WRITE_BUFFERED;
@@ -308,22 +320,15 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in)
308320

309321
if (r->limit_rate) {
310322

311-
nsent = c->sent;
323+
excess += (c->sent - sent);
312324

313-
if (r->limit_rate_after) {
325+
r->limit_last = ngx_current_msec;
326+
r->limit_excess = excess;
314327

315-
sent -= r->limit_rate_after;
316-
if (sent < 0) {
317-
sent = 0;
318-
}
319-
320-
nsent -= r->limit_rate_after;
321-
if (nsent < 0) {
322-
nsent = 0;
323-
}
324-
}
328+
excess -= (off_t) r->limit_rate_after + (off_t) r->limit_rate / 2;
329+
excess = ngx_max(excess, 0);
325330

326-
delay = (ngx_msec_t) ((nsent - sent) * 1000 / r->limit_rate);
331+
delay = (ngx_msec_t) (excess * 1000 / r->limit_rate);
327332

328333
if (delay > 0) {
329334
c->write->delayed = 1;

src/stream/ngx_stream_proxy_module.c

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -435,7 +435,6 @@ ngx_stream_proxy_handler(ngx_stream_session_t *s)
435435
}
436436

437437
u->peer.type = c->type;
438-
u->start_sec = ngx_time();
439438

440439
c->write->handler = ngx_stream_proxy_downstream_handler;
441440
c->read->handler = ngx_stream_proxy_downstream_handler;
@@ -924,6 +923,9 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s)
924923
u->upload_rate = ngx_stream_complex_value_size(s, pscf->upload_rate, 0);
925924
u->download_rate = ngx_stream_complex_value_size(s, pscf->download_rate, 0);
926925

926+
u->upload_last = ngx_current_msec;
927+
u->upload_excess = s->received;
928+
927929
u->connected = 1;
928930

929931
pc->read->handler = ngx_stream_proxy_upstream_handler;
@@ -1555,14 +1557,15 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
15551557
ngx_uint_t do_write)
15561558
{
15571559
char *recv_action, *send_action;
1558-
off_t *received, limit, sent;
1560+
off_t *received, *limit_excess, limit, excess, sent;
15591561
size_t size, limit_rate;
15601562
ssize_t n;
15611563
ngx_buf_t *b;
15621564
ngx_int_t rc;
15631565
ngx_uint_t flags, *packets;
1564-
ngx_msec_t delay;
1566+
ngx_msec_t *limit_last, delay;
15651567
ngx_chain_t *cl, **ll, **out, **busy;
1568+
ngx_msec_int_t ms;
15661569
ngx_connection_t *c, *pc, *src, *dst;
15671570
ngx_log_handler_pt handler;
15681571
ngx_stream_upstream_t *u;
@@ -1595,6 +1598,8 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
15951598
dst = c;
15961599
b = &u->upstream_buf;
15971600
limit_rate = u->download_rate;
1601+
limit_last = &u->download_last;
1602+
limit_excess = &u->download_excess;
15981603
received = &u->received;
15991604
packets = &u->responses;
16001605
out = &u->downstream_out;
@@ -1607,6 +1612,8 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
16071612
dst = pc;
16081613
b = &u->downstream_buf;
16091614
limit_rate = u->upload_rate;
1615+
limit_last = &u->upload_last;
1616+
limit_excess = &u->upload_excess;
16101617
received = &s->received;
16111618
packets = &u->requests;
16121619
out = &u->upstream_out;
@@ -1616,6 +1623,7 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
16161623
}
16171624

16181625
#if (NGX_SUPPRESS_WARN)
1626+
excess = 0;
16191627
sent = 0;
16201628
#endif
16211629

@@ -1652,12 +1660,19 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
16521660
if (size && src->read->ready && !src->read->delayed) {
16531661

16541662
if (limit_rate) {
1655-
limit = (off_t) limit_rate * (ngx_time() - u->start_sec + 1)
1656-
- *received;
1663+
ms = (ngx_msec_int_t) (ngx_current_msec - *limit_last);
1664+
ms = ngx_max(ms, 0);
1665+
1666+
excess = (off_t) (*limit_excess
1667+
- (uint64_t) limit_rate * ms / 1000);
1668+
excess = ngx_max(excess, 0);
1669+
1670+
limit = (off_t) limit_rate - excess;
16571671

16581672
if (limit <= 0) {
16591673
src->read->delayed = 1;
1660-
delay = (ngx_msec_t) (- limit * 1000 / limit_rate + 1);
1674+
excess -= (off_t) limit_rate / 2;
1675+
delay = (ngx_msec_t) (excess * 1000 / limit_rate + 1);
16611676
ngx_add_timer(src->read, delay);
16621677
break;
16631678
}
@@ -1682,7 +1697,15 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream,
16821697

16831698
if (n >= 0) {
16841699
if (limit_rate) {
1685-
delay = (ngx_msec_t) (n * 1000 / limit_rate);
1700+
excess += n;
1701+
1702+
*limit_last = ngx_current_msec;
1703+
*limit_excess = excess;
1704+
1705+
excess -= (off_t) limit_rate / 2;
1706+
excess = ngx_max(excess, 0);
1707+
1708+
delay = (ngx_msec_t) (excess * 1000 / limit_rate);
16861709

16871710
if (delay > 0) {
16881711
src->read->delayed = 1;

src/stream/ngx_stream_upstream.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,18 @@ typedef struct {
127127
ngx_chain_t *downstream_busy;
128128

129129
off_t received;
130-
time_t start_sec;
131130
ngx_uint_t requests;
132131
ngx_uint_t responses;
133132
ngx_msec_t start_time;
134133

135134
size_t upload_rate;
136135
size_t download_rate;
137136

137+
ngx_msec_t upload_last;
138+
ngx_msec_t download_last;
139+
off_t upload_excess;
140+
off_t download_excess;
141+
138142
ngx_str_t ssl_name;
139143

140144
ngx_stream_upstream_srv_conf_t *upstream;

0 commit comments

Comments
 (0)