fix: release file handles after truncated reads - #571
Conversation
|
Important Review skippedNo new commits to review since the last review. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughText file reads now close readline interfaces and underlying streams across end-based, start-based, and estimated-position paths. An end-to-end test verifies truncated reads release handles before file replacement. ChangesRead stream cleanup
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/utils/files/text.ts (1)
323-356: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
readFromEndWithReadlinestill leaks the stream handle.The
streamvariable was introduced (lines 323–325) but cleanup at line 339 still callsrl.close()directly instead ofcloseReadlineStream(rl, stream). The stream is never explicitly destroyed and itscloseevent is never awaited, so on Windows the file handle may still be open when the function returns — the exact bug this PR fixes.Additionally, there is no
try/finallyaround thefor awaitloop. If an error occurs mid-iteration (e.g.,AbortSignalaborts),rl.close()at line 339 is skipped entirely, leaking both the readline interface and the underlying stream.🔒 Proposed fix: wrap in try/finally and use closeReadlineStream
const buffer: string[] = new Array(requestedLines); let bufferIndex = 0; let totalLines = 0; - for await (const line of rl) { - buffer[bufferIndex] = line; - bufferIndex = (bufferIndex + 1) % requestedLines; - totalLines++; + try { + for await (const line of rl) { + buffer[bufferIndex] = line; + bufferIndex = (bufferIndex + 1) % requestedLines; + totalLines++; + } + } finally { + await closeReadlineStream(rl, stream); } - rl.close(); - let result: string[];🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/utils/files/text.ts` around lines 323 - 356, Update readFromEndWithReadline to wrap the for-await loop and result processing in try/finally, and perform cleanup in the finally block with closeReadlineStream(rl, stream) instead of calling rl.close() directly. Ensure cleanup runs for successful reads and iteration errors or aborts before the function exits.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/utils/files/text.ts`:
- Around line 323-356: Update readFromEndWithReadline to wrap the for-await loop
and result processing in try/finally, and perform cleanup in the finally block
with closeReadlineStream(rl, stream) instead of calling rl.close() directly.
Ensure cleanup runs for successful reads and iteration errors or aborts before
the function exits.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 80f0c028-3411-4f0d-b35c-19cc7b50b1a1
📒 Files selected for processing (2)
src/utils/files/text.tstest/test-read-file-handle-release.js
|
@coderabbitai review |
✅ Action performedReview finished.
|
Summary
closeevent before returningRoot cause
The bounded text-read paths could break out of
for awaitbefore reaching EOF. They closed the readline interface but did not explicitly destroy and await closure of the underlyingReadStream. On Windows, that stream could retain a file handle long enough to make an immediate atomic replacement fail withWinError 5.Impact
After
read_filereturns a prefix of a large text file, another process can immediately replace that path without being blocked by a handle retained by Desktop Commander.Fixes #476.
Validation
npm run buildnode test/test-read-file-handle-release.jsnode test/test-file-handlers.jsnode test/test-read-abort-timeout.js(4/4)Summary by CodeRabbit
Bug Fixes
Tests