-
-
Notifications
You must be signed in to change notification settings - Fork 763
fix(diagnostics): handle WouldBlock errors when writing to stdout #17238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
When oxlint is spawned as a child process (e.g., via Node.js execa), stdout is connected to a pipe. If the parent doesn't consume output fast enough, the pipe buffer fills and writes return WouldBlock (EAGAIN). Previously this caused a panic. Replace `write_all` with `write_all_retry` that properly handles: - WouldBlock: sleep 1ms and retry (back-pressure) - Interrupted: retry immediately - BrokenPipe: return gracefully - Partial writes: continue until all bytes written 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a panic in oxlint when writing to stdout pipes by implementing proper retry logic for transient I/O errors. When oxlint is used as a child process and the parent doesn't consume output fast enough, the pipe buffer fills and write operations return WouldBlock errors, which previously caused panics.
Key changes:
- Replaced
check_for_writer_errorwithwrite_all_retryandflush_retryfunctions that handleWouldBlock,Interrupted, andBrokenPipeerrors - Added 1ms sleep backoff for
WouldBlockretries to prevent busy-waiting - Implemented comprehensive unit tests with mock writers simulating various error conditions
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| .write_all(err_str.as_bytes()) | ||
| .or_else(Self::check_for_writer_error) | ||
| .unwrap(); | ||
| Self::write_all_retry(writer, err_str.as_bytes()).unwrap(); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value of write_all_retry (which indicates success or broken pipe via Ok(true) vs Ok(false)) is being ignored by calling unwrap() directly. Consider handling the Ok(false) case explicitly to distinguish between successful writes and broken pipe scenarios, or document why ignoring this distinction is acceptable.
| .write_all(err_str.as_bytes()) | ||
| .or_else(Self::check_for_writer_error) | ||
| .unwrap(); | ||
| Self::write_all_retry(writer, err_str.as_bytes()).unwrap(); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value of write_all_retry (which indicates success or broken pipe via Ok(true) vs Ok(false)) is being ignored by calling unwrap() directly. Consider handling the Ok(false) case explicitly to distinguish between successful writes and broken pipe scenarios, or document why ignoring this distinction is acceptable.
| .write_all(finish_output.as_bytes()) | ||
| .or_else(Self::check_for_writer_error) | ||
| .unwrap(); | ||
| Self::write_all_retry(writer, finish_output.as_bytes()).unwrap(); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value of write_all_retry (which indicates success or broken pipe via Ok(true) vs Ok(false)) is being ignored by calling unwrap() directly. Consider handling the Ok(false) case explicitly to distinguish between successful writes and broken pipe scenarios, or document why ignoring this distinction is acceptable.
| } | ||
|
|
||
| writer.flush().or_else(Self::check_for_writer_error).unwrap(); | ||
| Self::flush_retry(writer).unwrap(); |
Copilot
AI
Dec 22, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The return value of flush_retry (which indicates success or broken pipe via Ok(true) vs Ok(false)) is being ignored by calling unwrap() directly. Consider handling the Ok(false) case explicitly to distinguish between successful flush and broken pipe scenarios, or document why ignoring this distinction is acceptable.
| Self::flush_retry(writer).unwrap(); | |
| match Self::flush_retry(writer) { | |
| Ok(true) => { | |
| // Flushed successfully; nothing else to do. | |
| } | |
| Ok(false) => { | |
| // Broken pipe / consumer gone: stop writing and return the result. | |
| return result; | |
| } | |
| Err(e) => { | |
| // Preserve previous behavior of panicking on unexpected I/O errors. | |
| panic!("failed to flush diagnostics output: {e}"); | |
| } | |
| } |
CodSpeed Performance ReportMerging #17238 will not alter performanceComparing Summary
Footnotes
|
Summary
WouldBlockerrors with 1ms backoffInterruptedandBrokenPipegracefullyWhen oxlint is spawned as a child process (e.g., via Node.js execa/child_process), stdout is connected to a pipe. If the parent process doesn't consume output fast enough, the pipe buffer fills (8KB on macOS, 64KB on Linux) and writes return
WouldBlock(EAGAIN, error code 35 on macOS). Previously this caused a panic:This PR replaces
write_allwithwrite_all_retrythat properly handles transient I/O errors by retrying.Test plan
write_all_retryandflush_retry🤖 Generated with Claude Code