Skip to content

Fix flaky tcp_server specs under valgrind - #104

Merged
Watson1978 merged 1 commit into
socketry:mainfrom
Watson1978:stabilize-tcp-server-timeout-specs
Jul 31, 2026
Merged

Fix flaky tcp_server specs under valgrind#104
Watson1978 merged 1 commit into
socketry:mainfrom
Watson1978:stabilize-tcp-server-timeout-specs

Conversation

@Watson1978

Copy link
Copy Markdown
Collaborator

Problem

test_run and test_run_timeout send data, sleep TIMEOUT (10ms), then tear the server down and assert on what arrived:

send_data(data) if data
sleep timeout          # <- assumes accept + on_read both happen within 10ms
server.detach
running = false
thread.join
@data                  # nil if they didn't

Under valgrind the reactor is an order of magnitude slower, so 10ms is often not enough and the example fails with expected: "hello", got: nil.

Seen on the Memcheck workflow, job 4.0 on ubuntu: https://github.com/socketry/cool.io/actions/runs/30615723672/job/91108332098

The other four matrix jobs (3.2, 3.3, head, and 3.4 before fail-fast cancelled it) passed on the same commit, and the same job passes on main, so this is timing, not a regression.

Fix

Wait for the data instead of for the clock, wrapped in Timeout.timeout so the wait can never turn into an indefinite sleep:

WAIT_TIMEOUT = 5.0

def wait_until(timeout = WAIT_TIMEOUT)
  Timeout.timeout(timeout) do
    sleep 0.001 until yield
  end
end

Five seconds is generous enough that a slow machine still passes, and a genuine hang now raises Timeout::Error at the wait itself instead of surfacing later as a confusing nil comparison. Both helpers also reset @data first, so the wait condition cannot be satisfied by a value left over from an earlier example.

Only these two helpers are affected. test_run_once already blocks on thread.join, and the two run_once(timeout) examples do not assert on @data.

Verification

Shrinking TIMEOUT to 0.0001 stands in for valgrind's slowdown and reproduces the CI failure locally:

before: 6 runs -> 4 failed (./spec/tcp_server_spec.rb:127)
after: 10 runs -> 0 failed

The timeout path was checked separately with a condition that never becomes true, which raises Timeout::Error at the deadline rather than hanging.

rspec: 58 examples, 1 failure (detach_race_condition_spec.rb:35) — a pre-existing Windows failure unrelated to this change.

🤖 Generated with Claude Code

test_run and test_run_timeout send data, sleep TIMEOUT (10ms), then tear the
server down and assert on what arrived. That assumes the reactor accepts the
connection and dispatches on_read within those 10ms. Under valgrind it often
does not, and the example fails with `expected: "hello", got: nil`.

Seen on the Memcheck workflow, job "4.0 on ubuntu":
https://github.com/socketry/cool.io/actions/runs/30615723672/job/91108332098
The other four matrix jobs passed on the same commit.

Wait for the data instead of for the clock, under Timeout.timeout so the wait
can never become an indefinite sleep. Five seconds is generous enough that a
slow machine still passes, and a genuine hang now raises Timeout::Error at the
wait itself rather than surfacing later as a confusing nil comparison. Both
helpers also reset @DaTa first, so the wait condition cannot be satisfied by a
value left over from an earlier example.

Only these two helpers are affected. test_run_once already blocks on
thread.join, and the two run_once(timeout) examples do not assert on @DaTa.

Verified by shrinking TIMEOUT to 0.0001 to stand in for valgrind's slowdown,
which reproduces the CI failure locally:

  before: 6 runs -> 4 failed (./spec/tcp_server_spec.rb:127)
  after: 10 runs -> 0 failed

The timeout path was checked separately with a condition that never becomes
true, which raises Timeout::Error at the deadline instead of hanging.

rspec: 58 examples, 1 failure (detach_race_condition_spec.rb:35), a pre-existing
Windows failure unrelated to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Watson1978
Watson1978 merged commit 10b2fcc into socketry:main Jul 31, 2026
14 checks passed
@Watson1978
Watson1978 deleted the stabilize-tcp-server-timeout-specs branch July 31, 2026 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant