Fix flaky async_watcher_spec on macOS Ruby 3.2+#96
Merged
Watson1978 merged 1 commit intoJun 28, 2026
Conversation
IO.pipe on macOS Ruby 3.2+ returns pipes with O_NONBLOCK set by default. sysread(1) calls read() directly without EAGAIN retry, so the handshake raises Errno::EAGAIN when the child has not yet written before the parent reads. Replacing sysread with read lets Ruby's I/O layer handle EAGAIN transparently and wait for data. Reproduced without cool.io (25% EAGAIN rate in 200 attempts). Linux is unaffected because IO.pipe there does not set O_NONBLOCK. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
Problem
spec/async_watcher_spec.rbfails intermittently on macOS with Ruby 3.2+.The handshake on line 33 uses
rd.sysread(1)to wait for each child to become ready:Root cause (macOS Ruby 3.2+, platform-conditional)
On macOS Ruby 3.2+,
IO.pipereturns pipes withO_NONBLOCKalready set on both ends:This is unrelated to cool.io or libev — a bare
IO.pipecall with no other code loaded reproduces it.sysreadcallsread()directly without any EAGAIN retry, so if the child has not yet written'.'when the parent reads,Errno::EAGAINis raised.Reproduced without cool.io using a minimal script (100 iterations × 2 reads):
Linux is unaffected because
IO.pipethere does not setO_NONBLOCK.Fix
Replace
sysreadwithread, which handlesEAGAINinternally and waits transparently for data regardless of the fd's nonblocking flag.Assertions (
lines.size == nr_signal,uniq.size == nr_fork) are unchanged.Verification (macOS / Ruby 3.2.6)
bundle exec rspec spec/async_watcher_spec.rbpassed 10 consecutive runsbundle exec rspec spec/) passed: 44 examples, 0 failures🤖 Generated with Claude Code