Stop opening a null-device handle per Capture and spinner task - #629
Draft
GoodForOneFare wants to merge 1 commit into
Draft
Stop opening a null-device handle per Capture and spinner task#629GoodForOneFare wants to merge 1 commit into
GoodForOneFare wants to merge 1 commit into
Conversation
GoodForOneFare
force-pushed
the
gordo-capture-fd-leak
branch
3 times, most recently
from
August 17, 2026 12:45
2837642 to
7a22970
Compare
Capture#initialize and SpinGroup#add both defaulted duplicate_output_to to a freshly opened File::NULL handle. Every capture, including one per spinner task, therefore opened an OS handle solely to discard writes. Ruby closes these handles when their File objects are finalized, but they remain open while captures or queued task closures are live. Default duplicate_output_to to nil and skip the duplicate write when unset. Type the option as io_like? so StringIO remains supported, and ignore IOError from caller-owned duplicate streams to match the module-level duplicate writer. The regression tests compare process descriptor sets while captures and pending spinner tasks remain live, asserting only on additions so unrelated File finalization cannot make the tests flaky. They also cover explicit StringIO duplication through Capture and SpinGroup, merged stderr, and a duplicate stream closed during capture. Co-authored-by: River <river@shopify.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Assisted-By: devx/cceb8550-0aa0-4aa5-b0d0-bd55a129e07c
GoodForOneFare
force-pushed
the
gordo-capture-fd-leak
branch
from
August 17, 2026 14:06
7a22970 to
f74b41d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
Capture#initializeandSpinGroup#addboth defaultedduplicate_output_to:to a freshly openedFile::NULLhandle:Every capture therefore opened an OS handle solely to discard writes, including one for every spinner task. Ruby closes these handles when their
Fileobjects are finalized, but they remain open while captures or queued task closures are live.Verified on
main: constructing 10 captures opens 10 new descriptors.Fix
Default
duplicate_output_to:toniland skip the duplicate write when unset. This avoids opening a descriptor just to discard output.The option is typed as
io_like?(IO | StringIO | nil) so its signature matches the streams accepted elsewhere in cli-ui. Explicit duplicate streams continue to receive captured output and remain caller-owned. AnIOErrorfrom a closed duplicate stream is ignored, matching the module-level duplicate writer.The module-level
StdoutRouter.duplicate_output_toused byCLI::UI.log_output_tois a separate mechanism and is otherwise unchanged.Tests
Captureobjects add no descriptors.StringIOduplicates receive captured stdout through bothCaptureandSpinGroup#addand remain caller-owned.The descriptor tests inspect
/dev/fdon macOS and Linux and skip where it is unavailable. They compare descriptor sets and assert only that no descriptors were added, so unrelatedFileobjects finalized between samples cannot make the test fail merely because the total count decreased.🤖 Generated with Claude Code
(posted by an LLM bot on behalf of Gord)