bugfix: ngx.flush() buffer could be recycled while still queued - #2518
Open
setnicka wants to merge 1 commit into
Open
bugfix: ngx.flush() buffer could be recycled while still queued#2518setnicka wants to merge 1 commit into
setnicka wants to merge 1 commit into
Conversation
setnicka
force-pushed
the
fix-get-flush-chain
branch
from
August 11, 2026 10:00
4883551 to
8a8a71c
Compare
Buffer taken from ctx->free_bufs with tag and with size 0 would be returned to the ctx->free_bufs by ngx_chain_update_chains(). When reused, it results into memzeroing the flush flag and "zero size buf in writer t:1 r:0 f:0" alerts.
setnicka
force-pushed
the
fix-get-flush-chain
branch
from
August 11, 2026 14:45
8a8a71c to
9dd71a8
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
ngx_http_lua_get_flush_chain()takes the flush buffer fromctx->free_bufsand leaves the module tag on it.A flush buffer holds no data, so
ngx_buf_special()is what makes its zero size acceptable tongx_http_write_filter(), which may therefore keep it queued inr->out. Butngx_chain_update_chains()treatsngx_buf_size() == 0as consumed, so it returns the buffer toctx->free_bufswhile the writer still references it. The nextngx_http_lua_chain_get_free_buf()memzeros it and setstemporary, so it is no longer special and the writer finds a zero size non-special buffer. This results in:It returns
NGX_ERROR, so the request is terminated, and the response truncated. I also think that two chain links can possibly end up pointing at the same buffer, in which case its contents may be sent twice.The tag matters: an untagged flush buffer is skipped by
ngx_chain_update_chains()and never recycled, so only the tagged one is affected.How I ran into it
I used nginx as a caching reverse proxy, with Lua in the rewrite phase issuing cosocket requests and response bodies coming from the upstream API. Output was still queued in
r->out(slow clients, and clients abandoning transfers withproxy_ignore_client_abort on), whilectx->busy_bufswas empty. So the flush buffer was the head thengx_chain_update_chains()walks.No explicit
ngx.flush()is needed:ngx_http_lua_flush_pending_output()creates the flush chain exactly in thatctx->busy_bufs == NULLcase. The symptoms were truncated responses and alerts appearing a few times per hour per worker.Fix
Allocate the chain link and the buffer directly and leave the buffer untagged, so
ngx_chain_update_chains()drops the chain link without recycling the buffer. Cost is onengx_buf_tper flush, reclaimed with the request pool.Testing
No regression test. Triggering this needs
ctx->free_bufsnon-empty (so the buffer is tagged),ctx->busy_bufsempty, output still queued, and a later reuse. In synthetic tests, I was not able to reliably reproduce this reliably without stress testing with thousands of requests. The flush buffer is freshly allocated and therefore untagged, which is the unaffected state. The failure was confirmed on production traffic using debug logging that logged buffers returned toctx->free_bufswhile still linked inr->out.I had to adapt the 1st test from 056-flush.t because of the new allocation.
Related
#1028 reports the same defect with an independent analysis; it was closed without a fix.
The same pattern probably exists for the
last_buf/last_in_chain/flushmarkers inngx_http_lua_body_filter_param_set(); left unchanged until confirmed.I hereby granted the copyright of the changes in this pull request to the authors of this lua-nginx-module project.