AlinsRan opened a new pull request, #13581:
URL: https://github.com/apache/apisix/pull/13581

   ### Description
   
   Fixes #11244
   
   **Root cause**
   
   `core.response.hold_body_chunk` accumulated every response-body chunk into a 
Lua table (`{chunk, n, bytes, ...}`) and, when the size limit was reached or on 
`eof`, concatenated the whole table into one string with `table.concat`. For 
large bodies (the issue reports ~128K), this keeps *both* the list of 
individual chunks *and* the final concatenated string alive simultaneously, and 
each `table.concat` allocates a brand new string. The result is excessive 
allocation/retention that shows up as memory growth when loggers hold large 
response bodies.
   
   **Fix**
   
   Rewrite the accumulation to use a LuaJIT `string.buffer` 
(`require("string.buffer")`), reviving the approach from the abandoned #12035 
and adapting it to the current `hold_body_chunk` shape:
   
   - chunks are appended with `buffer:put(chunk)` instead of being stored in a 
growing Lua table;
   - the final body is produced with `buffer:get()` (full body) or 
`buffer:get(max_resp_body_bytes)` (truncated), which returns exactly the first 
`max_resp_body_bytes` bytes and avoids materializing an intermediate 
concatenated copy before truncation;
   - the wrapper table is kept only for the `bytes` counter and the `done` flag 
so the buffer object can carry per-`buffer_key` state.
   
   All existing semantics are preserved:
   - the `hold_the_copy` flag (still flushes `arg[1]` on non-final chunks when 
false);
   - `max_resp_body_bytes` truncation (returns exactly `max_resp_body_bytes`);
   - the `done` flag (later chunks and `eof` return `nil` after a truncated 
body was already returned);
   - the single-chunk-that-is-also-eof path;
   - per-`body_buffer_key` / `ctx._plugin_name` storage.
   
   **Test**
   
   Adds `t/core/response-hold-body.t` covering:
   1. multi-chunk accumulation of a 128K body (full body returned at eof);
   2. truncation at `max_resp_body_bytes` across multiple chunks;
   3. the `done` flag — later chunks and eof return `nil` after truncation;
   4. a single chunk that is also eof, truncated to `max_resp_body_bytes`;
   5. small-body single-chunk passthrough (mirrors the existing 
`t/core/response.t` TEST 8 mock pattern).
   
   **Verification**
   
   - `luacheck apisix/core/response.lua` passes (0 warnings / 0 errors).
   - All inline Lua blocks in the new `.t` parse cleanly.
   - The rewritten function was exercised end-to-end via a standalone harness 
that drives the same `ngx.arg[1]/[2]` chunk/eof protocol; all six scenarios 
above pass and return byte-identical bodies (e.g. truncation returns exactly 
131072 bytes).
   - A memory micro-benchmark inside test-nginx is not reliable, so the 
rationale is documented above; the `string.buffer` path avoids the simultaneous 
chunk-table + concatenated-string retention that caused the growth.
   
   > Note: the full `prove` test-nginx run was not executed in my local 
environment because the available OpenResty build (1.21.4.4) does not support 
the `quic`/`http3` listen directives that `t/APISIX.pm` emits unconditionally 
on master; this is an environment limitation, not a test failure. CI runs 
against a QUIC-capable OpenResty.
   
   ### Checklist
   
   - [x] I have explained the need for this PR and the problem it solves
   - [x] I have explained the changes or the new features added to this PR
   - [x] I have added tests corresponding to this change
   - [ ] I have updated the documentation to reflect this change
   - [x] I have verified that this change is backward compatible


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to