Skip to content

Commit 6071b2b

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 6071b2b

File tree

3 files changed

+61
-13
lines changed

3 files changed

+61
-13
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: 56 additions & 13 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
}
@@ -272,6 +269,41 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
272269
return NGX_OK;
273270
}
274271

272+
if (in != NULL) {
273+
if (ngx_chain_add_copy(r->pool, &ctx->in_bufs, in) != NGX_OK) {
274+
return NGX_ERROR;
275+
}
276+
}
277+
278+
if (ctx->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->busy_bufs, &out,
299+
(ngx_buf_tag_t) &ngx_http_lua_module);
300+
if (ctx->busy_bufs != NULL
301+
&& (r->connection->buffered
302+
& (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED))) {
303+
return rc;
304+
}
305+
}
306+
275307
if (ctx->cleanup == NULL) {
276308
cln = ngx_http_cleanup_add(r, 0);
277309
if (cln == NULL) {
@@ -286,6 +318,9 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
286318
old_context = ctx->context;
287319
ctx->context = NGX_HTTP_LUA_CONTEXT_BODY_FILTER;
288320

321+
in = ctx->in_bufs;
322+
ctx->in_bufs = NULL;
323+
289324
dd("calling body filter handler");
290325
rc = llcf->body_filter_handler(r, in);
291326

@@ -300,17 +335,25 @@ ngx_http_lua_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
300335
lmcf = ngx_http_get_module_main_conf(r, ngx_http_lua_module);
301336
out = lmcf->body_filter_chain;
302337

303-
if (in == out) {
304-
return ngx_http_next_body_filter(r, in);
305-
}
338+
if (in != out) {
339+
/* content of body was replaced in ngx_http_lua_body_filter_param_set
340+
* and the buffer was marked as consumed.
341+
*/
342+
for (cl = in; cl != NULL; cl = ln) {
343+
ln = cl->next;
344+
ngx_free_chain(r->pool, cl);
345+
}
306346

307-
if (out == NULL) {
308-
/* do not forward NULL to the next filters because the input is
309-
* not NULL */
310-
return NGX_OK;
347+
if (out == NULL) {
348+
/* do not forward NULL to the next filters because the input is
349+
* not NULL */
350+
return NGX_OK;
351+
}
352+
353+
} else {
354+
/* out = in; */
311355
}
312356

313-
/* in != out */
314357
rc = ngx_http_next_body_filter(r, out);
315358
if (rc == NGX_ERROR) {
316359
return NGX_ERROR;

src/ngx_http_lua_common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,7 @@ typedef struct ngx_http_lua_ctx_s {
547547
ngx_chain_t *free_bufs;
548548
ngx_chain_t *busy_bufs;
549549
ngx_chain_t *free_recv_bufs;
550+
ngx_chain_t *in_bufs; /* for the body filter */
550551

551552
ngx_http_cleanup_pt *cleanup;
552553

0 commit comments

Comments
 (0)