Skip to content

bugfix: buffer bloat and CPU 100% when download large file was filtered by body_filter_by_lua. #1909

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Sep 3, 2021

Conversation

zhuizhuhaomeng
Copy link
Contributor

@zhuizhuhaomeng zhuizhuhaomeng commented Aug 18, 2021

fix issue #1906
I hereby granted the copyright of the changes in this pull request
to the authors of this lua-nginx-module project.

}

for (ln = in; ln != NULL; ln = ln->next) {
cl = ngx_alloc_chain_link(r->pool);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should avoid alloc chain (and free chain below). Small memory blocks can never get freed in nginx memory pools. We should recycle them instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ngx_alloc_chain_link allocate chain from the chain pool first and ngx_free_chain free the chain to the chain pool.
ngx_chain_t *
ngx_alloc_chain_link(ngx_pool_t *pool)
{
ngx_chain_t *cl;

cl = pool->chain;

if (cl) {
    pool->chain = cl->next;
    return cl;
}

cl = ngx_palloc(pool, sizeof(ngx_chain_t));
if (cl == NULL) {
    return NULL;
}

return cl;

}

#define ngx_free_chain(pool, cl)
(cl)->next = (pool)->chain;
(pool)->chain = (cl)

for (cl = in; cl != NULL; cl = ln) {
ln = cl->next;
if (ngx_buf_size(cl->buf) == 0) {
ngx_free_chain(r->pool, cl);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see explanation above.

*/
for (cl = in; cl != NULL; cl = ln) {
ln = cl->next;
if (ngx_buf_size(cl->buf) == 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not look right to me. 0-sized can be special bufs indicating flush, eof, and etc.

@waterdeng
Copy link

It works for #1906.

Thank you .

@zhuizhuhaomeng zhuizhuhaomeng force-pushed the body_filter branch 3 times, most recently from 6071b2b to 8dc45ef Compare August 29, 2021 13:14
@zhuizhuhaomeng zhuizhuhaomeng force-pushed the body_filter branch 4 times, most recently from c8fe8aa to b71d186 Compare August 29, 2021 15:28
…ed 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]
Comment on lines 274 to 275
if (in != NULL) {
if (ngx_chain_add_copy(r->pool, &ctx->filter_in_bufs, in) != NGX_OK) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (in != NULL) {
if (ngx_chain_add_copy(r->pool, &ctx->filter_in_bufs, in) != NGX_OK) {
if (in != NULL
&& ngx_chain_add_copy(r->pool, &ctx->filter_in_bufs, in) != NGX_OK)
{

Comment on lines 302 to 305
if (rc != NGX_OK) {
if (ctx->filter_busy_bufs != NULL
&& (r->connection->buffered
& (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (rc != NGX_OK) {
if (ctx->filter_busy_bufs != NULL
&& (r->connection->buffered
& (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED))) {
if (rc != NGX_OK
&& ctx->filter_busy_bufs != NULL
&& (r->connection->buffered
& (NGX_HTTP_LOWLEVEL_BUFFERED | NGX_LOWLEVEL_BUFFERED)))
{

@@ -548,6 +548,9 @@ typedef struct ngx_http_lua_ctx_s {
ngx_chain_t *busy_bufs;
ngx_chain_t *free_recv_bufs;

ngx_chain_t *filter_in_bufs; /* for the body filter */
ngx_chain_t *filter_busy_bufs; /* for the body filter */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use union here to save memory?
like:

union {
    ngx_chain_t *filter_busy_bufs;
    ngx_chain_t *busy_bufs;
};

Maybe filter_in_bufs can union with free_recv_bufs?

ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"%s has %sbusy bufs",
newline ? "lua say response" : "lua print response",
ctx->busy_bufs != NULL ? "" : "not ");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ctx->busy_bufs != NULL ? "" : "not ");
ctx->busy_bufs != NULL ? "" : "no ");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants