Use StringBuf in Response.chunked to fix O(n^2) loop#38
Conversation
Each iteration called String.append which copies the entire accumulated buffer, making chunked encoding quadratic in the number of chunks. StringBuf (already available via the http dependency) gives amortized O(1) appends.
There was a problem hiding this comment.
Build & Tests
Build: pre-existing failure on arm-linux (clang const-qualifier warnings in datetime dependency's TM struct — unrelated to this PR). Type checker (--check) passes. CI passes on macOS.
Tests: N/A — no test files exist in this repo.
Findings
-
Memory leak: missing
StringBuf.deletecall (web.carp:~366).StringBufis aregister-typethat does not implementdrop, so Carp will not auto-free it when it goes out of scope. Every otherStringBufusage in the dependency chain (e.g.http/http.carp:227,http/http.carp:384) follows the pattern:(let-do [body (StringBuf.to-string &sb)] (StringBuf.delete sb) body)The PR calls
(StringBuf.to-string &sb)inline without ever callingStringBuf.delete.to-stringcopies the data into a new heap-allocated String but does not free the StringBuf's internaldatabuffer. This leaks on every chunked response. Fix: bind theto-stringresult in alet-do, call(StringBuf.delete sb), then pass the body string toinit. -
Chunked encoding format is correct. Matches RFC 7230 §4.1. Edge cases (empty chunks array, zero-length chunk) are handled correctly or are pre-existing concerns.
-
CHANGELOG entry is accurate and well-placed.
-
StringBuf availability confirmed — comes transitively through
http→strbuf.
Verdict: revise
The optimization is correct and worthwhile, but the missing StringBuf.delete is a per-response memory leak in a server library. Two-line fix following the established pattern from http/http.carp.
|
Addressed the memory leak identified by @carpentry-reviewer: added |
There was a problem hiding this comment.
Build & Tests
Build: type checker passes. C compilation fails with pre-existing datetime dependency TM struct errors (const char * / String mismatch in tm_zone) — unrelated to this PR.
Tests: N/A — no test directory exists in this repo.
Prior feedback
The previous review flagged a memory leak from missing StringBuf.delete. This has been addressed: the current code uses (let-do [body (StringBuf.to-string &sb)] (StringBuf.delete sb) ...), matching the established pattern at http/http.carp:227 and http/http.carp:384 exactly.
Findings
None. Verified:
-
Ownership is clean.
sbis created, borrowed via&sbfor append operations, borrowed again forto-string(which copies the data), then ownership is transferred todelete(which frees the internal buffer). No leak, no double-free. -
Types are correct.
chunkfromArray.unsafe-nthis&String, matchingappend-str's second parameter.&(fmt ...)borrows the temporary owned String for the append.bodyfromto-stringis an owned String passed toinit. -
Chunked encoding format matches RFC 7230 §4.1.
<hex-length>\r\n<data>\r\nper chunk,0\r\n\r\nterminator. Pre-existing edge case: a zero-length chunk in the input array would produce0\r\n\r\nmid-stream (i.e. the terminator), which a strict parser would interpret as end-of-body. This is a pre-existing concern from the old code — the new code preserves the same behavior. -
CHANGELOG entry is accurate and correctly placed under "Changed".
Verdict: merge
The optimization is correct and the memory leak fix follows the established pattern. The pre-existing datetime build failure is unrelated.
Summary
Response.chunkedbuilt the encoded body by callingString.appendin a loop — each call copies the entire accumulated buffer, making encoding quadratic in the number of chunks. This replaces the loop withStringBuf(already available transitively via thehttpdependency), which gives amortized O(1) appends.The generated C and type checker confirm correctness. The full test suite has a pre-existing build failure in the
datetimedependency's TM struct (unrelated to this change).Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.