fix: pass through plain ReadableStream to fix cancel and perf#1816
fix: pass through plain ReadableStream to fix cancel and perf#1816eL1fe wants to merge 2 commits intoelysiajs:mainfrom
Conversation
When a handler returns a ReadableStream, Elysia was wrapping it in a new ReadableStream with per-chunk async iteration, causing ~100x latency for large streams (elysiajs#1741) and preventing the cancel callback from firing on client disconnect (elysiajs#1768). Plain ReadableStreams (non-SSE, non-generator) are now piped through a lightweight TransformStream that only normalizes non-standard chunk types (DataView, Blob) while passing Uint8Array/string/ArrayBuffer chunks through with minimal overhead. Fixes elysiajs#1741, fixes elysiajs#1768
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughEnhanced Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Handler as createStreamHandler
participant Stream as ReadableStream
participant Transform as TransformStream
participant Response as Response
Client->>Handler: sends request / expects Response
Handler->>Stream: inspects returned body
alt native ReadableStream (not SSE/generator)
Handler->>Transform: pipeThrough(type-aware transformer)
Transform->>Response: attach transformed stream
Response-->>Client: streamed response (pass-through)
else non-native / generator / SSE
Handler->>Handler: wrap via async-iterator (for-await)
Handler->>Response: build iterator-backed stream
Response-->>Client: streamed response (iterator path)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/adapter/utils.ts (1)
343-348: Consider adding try/catch aroundJSON.stringifyfor consistency.The existing
pullhandler (lines 435-442) catchesJSON.stringifyerrors and falls back tochunk.toString(). The new transform path doesn't, which could throw on edge cases like circular references or BigInt values.While plain
ReadableStreams typically emit binary data rather than objects, adding the fallback would maintain consistent behavior.♻️ Suggested fix
else - controller.enqueue( - typeof chunk === 'object' - ? JSON.stringify(chunk) - : String(chunk) - ) + try { + controller.enqueue( + typeof chunk === 'object' + ? JSON.stringify(chunk) + : String(chunk) + ) + } catch { + controller.enqueue(String(chunk)) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/adapter/utils.ts` around lines 343 - 348, The transform path currently calls controller.enqueue(typeof chunk === 'object' ? JSON.stringify(chunk) : String(chunk)) and can throw on JSON.stringify errors (e.g., circular refs); wrap the JSON.stringify call in a try/catch and on error fall back to chunk.toString() (or String(chunk)) before calling controller.enqueue so behavior matches the existing pull handler's fallback; update the transform branch around controller.enqueue and references to chunk to use this safe stringify pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/adapter/utils.ts`:
- Around line 343-348: The transform path currently calls
controller.enqueue(typeof chunk === 'object' ? JSON.stringify(chunk) :
String(chunk)) and can throw on JSON.stringify errors (e.g., circular refs);
wrap the JSON.stringify call in a try/catch and on error fall back to
chunk.toString() (or String(chunk)) before calling controller.enqueue so
behavior matches the existing pull handler's fallback; update the transform
branch around controller.enqueue and references to chunk to use this safe
stringify pattern.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 99ae9042-f710-49cf-8c93-768b8774615d
📒 Files selected for processing (1)
src/adapter/utils.ts
Matches the existing fallback pattern in the pull handler for edge cases like circular references or BigInt values.
Summary
ReadableStreamfrom a handler caused ~100x latency for large payloads (Returning aReadableStreamresults in 100% CPU and >100x latency #1741) and thecancel()callback never fired on client disconnect (ReadableStreamcancel callback never invoked on client disconnect #1768)Problem
The re-wrapping added per-chunk async overhead (
iterator.next()+ format + enqueue) and broke the native stream cancel propagation.Changes
src/adapter/utils.ts: detect plain ReadableStreams (non-SSE, non-generator) and pipe them through a minimal TransformStream that only normalizes non-standard chunk types (DataView, Blob → Uint8Array) while passing standard chunks through directlyTest plan
Fixes #1741, fixes #1768
Summary by CodeRabbit