Conversation
The asyncIterator acquired a reader with getReader() (locking the stream) but never released it, and had no try/finally. When a consumer broke, returned, or threw out of a for-await loop early, the reader's lock leaked and the stream could not be re-acquired or cancelled — the underlying piped stream stayed alive and buffered until it drained naturally. Wrap the read loop in try/finally and call reader.releaseLock() in finally so the lock is released on both normal completion and early exit. Co-Authored-By: Claude <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough修改了 XStream 异步迭代器的退出处理:正常结束时标记完成,提前退出时取消底层读取,并始终释放 reader 锁。新增测试验证提前 break 后锁已释放,且后续读取返回结束状态。 ChangesReader 锁释放修复
Estimated code review effort: 2 (Simple) | ~10 minutes Sequence Diagram(s)sequenceDiagram
participant Consumer
participant XStreamIterator
participant Reader
Consumer->>XStreamIterator: for await...of 消费数据
XStreamIterator->>Reader: reader.read()
Reader-->>XStreamIterator: { done, value }
alt done 为 true
XStreamIterator->>XStreamIterator: completed = true
else 继续产出
XStreamIterator-->>Consumer: yield value
end
Consumer->>XStreamIterator: break(提前退出)
XStreamIterator->>Reader: reader.cancel()
XStreamIterator->>Reader: reader.releaseLock()
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
Bundle ReportChanges will increase total bundle size by 82 bytes (0.0%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: antdx-array-pushAssets Changed:
|
There was a problem hiding this comment.
Code Review
This pull request wraps the custom async iterator of XStream in a try...finally block to ensure the reader lock is released when a consumer exits the loop early, and adds a corresponding test. The reviewer points out that simply releasing the lock does not cancel the underlying stream, which can lead to resource leaks (e.g., active network requests continuing in the background). The reviewer suggests tracking whether the iteration completed and calling reader.cancel() in the finally block if it was exited early, aligning the behavior with the standard ReadableStream async iterator.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| try { | ||
| while (true) { | ||
| const { done, value } = await reader.read(); | ||
|
|
||
| if (done) break; | ||
| if (done) break; | ||
|
|
||
| if (!value) continue; | ||
| if (!value) continue; | ||
|
|
||
| // Transformed data through all transform pipes | ||
| yield value; | ||
| // Transformed data through all transform pipes | ||
| yield value; | ||
| } | ||
| } finally { | ||
| // Always release the lock so the stream can be re-acquired or cancelled, | ||
| // even when the consumer breaks/returns/throws out of the loop early. | ||
| reader.releaseLock(); | ||
| } |
There was a problem hiding this comment.
While releasing the reader lock on early exit is a great fix, simply releasing the lock does not cancel the underlying stream.
According to the Streams Standard, the default async iterator of a ReadableStream automatically cancels the stream when the loop is exited early.
Since XStream overrides Symbol.asyncIterator with a custom implementation, if a consumer breaks, returns, or throws out of the for await loop early, the stream will remain active and continue fetching/buffering data in the background (e.g., an active network request for LLM token generation will keep running, wasting server resources and bandwidth).
To align with standard ReadableStream behavior and prevent resource leaks, we should also cancel the reader in the finally block if the stream iteration did not complete normally.
let completed = false;
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
completed = true;
break;
}
if (!value) continue;
// Transformed data through all transform pipes
yield value;
}
} finally {
if (!completed) {
reader.cancel().catch(() => {});
}
// Always release the lock so the stream can be re-acquired or cancelled,
// even when the consumer breaks/returns/throws out of the loop early.
reader.releaseLock();
}
Deploying ant-design-x with
|
| Latest commit: |
ec16d78
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://4f6c699c.ant-design-x.pages.dev |
| Branch Preview URL: | https://fix-x-sdk.ant-design-x.pages.dev |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1970 +/- ##
=======================================
Coverage 97.06% 97.06%
=======================================
Files 159 159
Lines 5755 5762 +7
Branches 1684 1714 +30
=======================================
+ Hits 5586 5593 +7
Misses 167 167
Partials 2 2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
size-limit report 📦
|
The custom Symbol.asyncIterator only released the reader lock on early exit (break/return/throw), which unlike the default ReadableStream async iterator left the underlying stream active — so an in-flight fetch (e.g. LLM token generation) kept buffering data in the background. Track a 'completed' flag and cancel the reader in the finally block when the loop did not finish normally, aligning with standard ReadableStream semantics and preventing server/bandwidth waste. cancel() runs before releaseLock() (needs the lock) and swallows rejection via catch. Co-Authored-By: Claude <noreply@anthropic.com>
The asyncIterator acquired a reader with getReader() (locking the stream) but never released it, and had no try/finally. When a consumer broke, returned, or threw out of a for-await loop early, the reader's lock leaked and the stream could not be re-acquired or cancelled — the underlying piped stream stayed alive and buffered until it drained naturally.
Wrap the read loop in try/finally and call reader.releaseLock() in finally so the lock is released on both normal completion and early exit.
中文版模板 / Chinese template
🤔 This is a ...
🔗 Related Issues
💡 Background and Solution
📝 Change Log
Summary by CodeRabbit
for await...of中提前break/return/throw时,XStream读取锁可能未释放导致后续无法正确取消或重新获取的问题。locked恢复,并验证底层读取已结束(读取返回done === true),同时完成锁释放。