Skip to content

Commit 8dc45ef

Browse files
bugfix: buffer bloat and CPU 100% when download large file was filtered by body_filter_by_lua.
When http body was change by lua script, ngx_http_lua_body_filter_param_set will mark the origin buf as consumed with code "cl->buf->pos = cl->buf->last". Because the buf was marked as consumed, the function ngx_output_chain will continue to write data to the output filter. When kernel socket send buffer is full, write to the socket will return EAGAIN. And the output buf will be cached in output chain. The output chain becomes longer and longer. Function ngx_chain_update_chains do an linear iteration of the output chain to put the new buf to the end of the chain. At the end, there are thounsands of bufs in the output chain which cause the CPU utilization become 100% and memory continue to grow up. nginx.conf: location /download { alias download; header_filter_by_lua_block { ngx.header.content_length = nil } body_filter_by_lua_block { ngx.arg[1]=ngx.arg[1] } } create a 2G file with the following cmd: mkdir -p /usr/local/openresty/nginx/download dd if=/dev/zero of=/usr/local/openresty/nginx/download/2G count=4096000 reproduce with the following cmd: curl -o /dev/null http://127.0.0.1/download/2G typical call stacks: 42706f: ngx_chain_update_chains[14] 47e993: ngx_http_chunked_body_filter[14] 483024: ngx_http_gzip_body_filter[14] 4864d3: ngx_http_ssi_body_filter[14] 489668: ngx_http_charset_body_filter[14] 48b86c: ngx_http_addition_body_filter[14] 48bf9c: ngx_http_gunzip_body_filter[14] 48d75e: ngx_http_trailers_filter[14] 4fb63c: ngx_http_lua_capture_body_filter[14] 509cb9: ngx_http_lua_body_filter[14] 42739e: ngx_output_chain[14] 48e1aa: ngx_http_copy_filter[14] 45ea2b: ngx_http_output_filter[14] 496569: ngx_http_static_handler[14] 45ec6e: ngx_http_core_content_phase[14] 459a25: ngx_http_core_run_phases[14] 463ed3: ngx_http_process_request[14] 4643ff: ngx_http_process_request_headers[14] 4647a6: ngx_http_process_request_line[14] 44c1fe: ngx_epoll_process_events[14] 4431c3: ngx_process_events_and_timers[14] 44a65a: ngx_worker_process_cycle[14] 449074: ngx_spawn_process[14] 44aaac: ngx_start_worker_processes[14] 44b213: ngx_master_process_cycle[14] 422df2: main[14] 23493: __libc_start_main[2] 422e5e: _start[14] 42706f: ngx_chain_update_chains[14] 509ce7: ngx_http_lua_body_filter[14] 42739e: ngx_output_chain[14] 48e1aa: ngx_http_copy_filter[14] 45ea2b: ngx_http_output_filter[14] 496569: ngx_http_static_handler[14] 45ec6e: ngx_http_core_content_phase[14] 459a25: ngx_http_core_run_phases[14] 463ed3: ngx_http_process_request[14] 4643ff: ngx_http_process_request_headers[14] 4647a6: ngx_http_process_request_line[14] 44c1fe: ngx_epoll_process_events[14] 4431c3: ngx_process_events_and_timers[14] 44a65a: ngx_worker_process_cycle[14] 449074: ngx_spawn_process[14] 44aaac: ngx_start_worker_processes[14] 44b213: ngx_master_process_cycle[14] 422df2: main[14] 23493: __libc_start_main[2] 422e5e: _start[14] 47d51f: ngx_http_write_filter[14] 47e973: ngx_http_chunked_body_filter[14] 483024: ngx_http_gzip_body_filter[14] 4864d3: ngx_http_ssi_body_filter[14] 489668: ngx_http_charset_body_filter[14] 48b86c: ngx_http_addition_body_filter[14] 48bf9c: ngx_http_gunzip_body_filter[14] 48d75e: ngx_http_trailers_filter[14] 4fb63c: ngx_http_lua_capture_body_filter[14] 509cb9: ngx_http_lua_body_filter[14] 42739e: ngx_output_chain[14] 48e1aa: ngx_http_copy_filter[14] 45ea2b: ngx_http_output_filter[14] 496569: ngx_http_static_handler[14] 45ec6e: ngx_http_core_content_phase[14] 459a25: ngx_http_core_run_phases[14] 463ed3: ngx_http_process_request[14] 4643ff: ngx_http_process_request_headers[14] 4647a6: ngx_http_process_request_line[14] 44c1fe: ngx_epoll_process_events[14] 4431c3: ngx_process_events_and_timers[14] 44a65a: ngx_worker_process_cycle[14] 449074: ngx_spawn_process[14] 44aaac: ngx_start_worker_processes[14] 44b213: ngx_master_process_cycle[14] 422df2: main[14] 23493: __libc_start_main[2] 422e5e: _start[14]
1 parent 8981872 commit 8dc45ef

5 files changed

+135
-15
lines changed

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
dist: xenial
22

3+
branches:
4+
only:
5+
- "master"
6+
37
os: linux
48

59
language: c

src/ngx_http_lua_bodyfilterby.c

Lines changed: 66 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -235,18 +235,15 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
235235
uint16_t old_context;
236236
ngx_http_cleanup_t *cln;
237237
ngx_chain_t *out;
238+
ngx_chain_t *cl, *ln;
238239
ngx_http_lua_main_conf_t *lmcf;
239240

240241
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
241242
"lua body filter for user lua code, uri \"%V\"", &r->uri);
242243

243-
if (in == NULL) {
244-
return ngx_http_next_body_filter(r, in);
245-
}
246-
247244
llcf = ngx_http_get_module_loc_conf(r, ngx_http_lua_module);
248245

249-
if (llcf->body_filter_handler == NULL) {
246+
if (llcf->body_filter_handler == NULL || r->header_only) {
250247
dd("no body filter handler found");
251248
return ngx_http_next_body_filter(r, in);
252249
}
@@ -269,7 +266,48 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
269266
in->buf->file_pos = in->buf->file_last;
270267
}
271268

272-
return NGX_OK;
269+
in = NULL;
270+
}
271+
272+
if (in != NULL) {
273+
if (ngx_chain_add_copy(r->pool, &ctx->filter_in_bufs, in) != NGX_OK) {
274+
return NGX_ERROR;
275+
}
276+
}
277+
278+
if (ctx->filter_busy_bufs != NULL
279+
&& (r->connection->buffered
280+
& (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED)))
281+
{
282+
/* Socket write buffer was full on last write.
283+
* Try to write the remain data, if still can not write
284+
* do not execute body_filter_by_lua otherwise the `in` chain will be
285+
* replaced by new content from lua and buf of `in` mark as consumed.
286+
* And then ngx_output_chain will call the filter chain again which
287+
* make all the data cached in the memory and long ngx_chain_t link
288+
* cause CPU 100%.
289+
*/
290+
rc = ngx_http_next_body_filter(r, NULL);
291+
292+
if (rc == NGX_ERROR) {
293+
return rc;
294+
}
295+
296+
out = NULL;
297+
ngx_chain_update_chains(r->pool,
298+
&ctx->free_bufs, &ctx->filter_busy_bufs, &out,
299+
(ngx_buf_tag_t) &ngx_http_lua_module);
300+
if (rc != NGX_OK) {
301+
if (ctx->filter_busy_bufs != NULL
302+
&& (r->connection->buffered
303+
& (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED))) {
304+
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
305+
"waiting body filter busy buffer to be sent");
306+
return NGX_AGAIN;
307+
}
308+
}
309+
310+
/* continue to process bufs in ctx->filter_in_bufs */
273311
}
274312

275313
if (ctx->cleanup == NULL) {
@@ -286,6 +324,9 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
286324
old_context = ctx->context;
287325
ctx->context = NGX_HTTP_LUA_CONTEXT_BODY_FILTER;
288326

327+
in = ctx->filter_in_bufs;
328+
ctx->filter_in_bufs = NULL;
329+
289330
dd("calling body filter handler");
290331
rc = llcf->body_filter_handler(r, in);
291332

@@ -298,26 +339,36 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
298339
}
299340

300341
lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module);
342+
343+
/* lmcf->body_filter_chain is the new buffer chain if body_filter_by_lua*
344+
* set new body content via ngx.arg[1] = new_content otherwise it is the
345+
* original `in` buffer chain.
346+
*/
301347
out = lmcf->body_filter_chain;
302348

303-
if (in == out) {
304-
return ngx_http_next_body_filter(r, in);
305-
}
349+
if (in != out) {
350+
/* content of body was replaced in ngx_http_lua_body_filter_param_set
351+
* and the buffers was marked as consumed.
352+
*/
353+
for (cl = in; cl != NULL; cl = ln) {
354+
ln = cl->next;
355+
ngx_free_chain(r->pool, cl);
356+
}
306357

307-
if (out == NULL) {
308-
/* do not forward NULL to the next filters because the input is
309-
* not NULL */
310-
return NGX_OK;
358+
if (out == NULL) {
359+
/* do not forward NULL to the next filters because the input is
360+
* not NULL */
361+
return NGX_OK;
362+
}
311363
}
312364

313-
/* in != out */
314365
rc = ngx_http_next_body_filter(r, out);
315366
if (rc == NGX_ERROR) {
316367
return NGX_ERROR;
317368
}
318369

319370
ngx_chain_update_chains(r->pool,
320-
&ctx->free_bufs, &ctx->busy_bufs, &out,
371+
&ctx->free_bufs, &ctx->filter_busy_bufs, &out,
321372
(ngx_buf_tag_t) &ngx_http_lua_module);
322373

323374
return rc;

src/ngx_http_lua_common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,9 @@ typedef struct ngx_http_lua_ctx_s {
548548
ngx_chain_t *busy_bufs;
549549
ngx_chain_t *free_recv_bufs;
550550

551+
ngx_chain_t *filter_in_bufs; /* for the body filter */
552+
ngx_chain_t *filter_busy_bufs; /* for the body filter */
553+
551554
ngx_http_cleanup_pt *cleanup;
552555

553556
ngx_http_cleanup_t *free_cleanup; /* free list of cleanup records */

src/ngx_http_lua_output.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,11 @@ ngx_http_lua_ngx_echo(lua_State *L, unsigned newline)
244244
return 2;
245245
}
246246

247+
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
248+
"%s has %sbusy bufs",
249+
newline ? "lua say response" : "lua print response",
250+
ctx->busy_bufs != NULL ? "" : "not ");
251+
247252
dd("downstream write: %d, buf len: %d", (int) rc,
248253
(int) (b->last - b->pos));
249254

t/082-body-filter-2.t

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# vim:set ft= ts=4 sw=4 et fdm=marker:
2+
3+
BEGIN {
4+
$ENV{TEST_NGINX_POSTPONE_OUTPUT} = 1;
5+
$ENV{TEST_NGINX_EVENT_TYPE} = 'poll';
6+
$ENV{MOCKEAGAIN}='world'
7+
}
8+
9+
use Test::Nginx::Socket::Lua;
10+
11+
#worker_connections(1014);
12+
#master_process_enabled(1);
13+
#log_level('warn');
14+
15+
log_level('debug');
16+
17+
repeat_each(2);
18+
19+
plan tests => repeat_each() * (blocks() * 5);
20+
21+
#no_diff();
22+
no_long_string();
23+
24+
run_tests();
25+
26+
__DATA__
27+
28+
=== TEST 1: check ctx->busy_bufs
29+
--- config
30+
location /t {
31+
postpone_output 1;
32+
content_by_lua_block {
33+
for i = 1, 5 do
34+
ngx.say(i, ": Hello World!")
35+
end
36+
}
37+
38+
body_filter_by_lua_block {
39+
ngx.arg[1] = ngx.arg[1]
40+
}
41+
}
42+
--- request
43+
GET /t
44+
--- response_body
45+
1: Hello World!
46+
2: Hello World!
47+
3: Hello World!
48+
4: Hello World!
49+
5: Hello World!
50+
51+
--- error_log
52+
waiting body filter busy buffer to be sent
53+
lua say response has busy bufs
54+
--- no_error_log
55+
[error]
56+
57+

0 commit comments

Comments
 (0)