Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
46027c0b95 fix(server): destroy response stream sink in all code paths
The ResponseStream.JSSink allocated in doRenderStream was not being
destroyed in several code paths, causing a memory leak:

1. When isAbortedOrEnded() is true after stream assignment - only
   finalize() was called, not destroy()

2. When stream is in progress but no Promise is returned - the sink
   was never destroyed

3. When abort() is called on a request with an active sink - abort()
   only calls finalize(), not destroy()

Added destroy() calls in all these paths, and added a safety net in
finalizeWithoutDeinit() to clean up any leaked sinks before the
RequestContext is returned to the pool.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-19 23:40:47 +00:00

View File

@@ -752,6 +752,16 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
stream.unpipeWithoutDeref();
}
// Clean up response stream sink if it wasn't already destroyed
// This can happen if the connection was aborted during streaming
if (this.sink) |wrapper| {
ctxLog("finalizeWithoutDeinit: cleaning up leaked sink", .{});
wrapper.sink.finalize();
wrapper.detach(globalThis);
this.sink = null;
wrapper.sink.destroy();
}
this.response_body_readable_stream_ref.deinit();
if (!this.pathname.isEmpty()) {
@@ -1270,6 +1280,8 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
response_stream.sink.onFirstWrite = null;
response_stream.sink.finalize();
this.sink = null;
response_stream.sink.destroy();
return;
}
var response_body_readable_stream_ref = this.response_body_readable_stream_ref;
@@ -1295,6 +1307,9 @@ pub fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool,
response_stream.detach(globalThis);
stream.cancel(globalThis);
response_stream.sink.markDone();
response_stream.sink.finalize();
this.sink = null;
response_stream.sink.destroy();
this.renderMissing();
}