Skip to content

fix(streams): wait for tcpip client connect instead of retrying - #3964

Draft
EmilyRagan wants to merge 1 commit into
mainfrom
fix/tcpip-client-connect-ealready
Draft

EmilyRagan wants to merge 1 commit into
mainfrom
fix/tcpip-client-connect-ealready

Conversation

@EmilyRagan

Copy link
Copy Markdown
Contributor

Summary

TcpipClientStream._connect (Python) spun on a non-blocking socket.connect() and treated EALREADY as success. If the handshake was still pending when the retry ran, the socket got EALREADY and the stream reported connected, even when the connection was later refused.

  • On Linux, a refused connection to localhost fails right away. The first retry gets ECONNREFUSED, so CI (ubuntu-latest) passes test_raises_a_timeout_when_unable_to_connect.
  • On macOS the refusal arrives later. The retry gets EALREADY first, and the test fails with ConnectionRefusedError not raised.

Fix

Follow the Ruby connect_nonblock flow:

  • Call connect_ex. On EINPROGRESS/EWOULDBLOCK, select for writable (plus exceptional, for Windows) with connect_timeout. That parameter was accepted before but never used.
  • Read SO_ERROR for the result. A non-zero errno is raised as OSError, which maps to its subclass (for example ConnectionRefusedError).
  • If the handshake doesn't finish before the timeout, raise TimeoutError("Connect timeout ...").
  • If the socket is closed during connect (ValueError/EBADF/ENOTSOCK), raise RuntimeError("Connect canceled"), as Ruby does.
  • The busy loop no longer burns CPU while the handshake is pending.

Tests

New tests in test/streams/test_tcpip_client_stream.py: refused connection on an unused port, a successful connect to a listening server, the timeout path and the canceled path.

On macOS, test/streams and test/interfaces pass (437 tests). Before the fix, test_raises_a_timeout_when_unable_to_connect failed 3 out of 3 runs. It now passes.

🤖 Generated with Claude Code

Retrying a non-blocking connect() treated EALREADY as connected. On macOS
a refused connect still pending on retry returns EALREADY, so the client
reported success. Wait for writability with connect_timeout (previously
ignored) and read SO_ERROR for the real outcome.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.15%. Comparing base (e3a728f) to head (8c84082).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3964      +/-   ##
==========================================
+ Coverage   80.13%   80.15%   +0.02%     
==========================================
  Files         901      901              
  Lines       68370    68374       +4     
  Branches     2699     2699              
==========================================
+ Hits        54789    54808      +19     
+ Misses      12913    12901      -12     
+ Partials      668      665       -3     
Flag Coverage Δ
frontend 67.05% <ø> (+0.06%) ⬆️
python 80.16% <ø> (+0.03%) ⬆️
ruby-api 82.59% <ø> (ø)
ruby-backend 85.66% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sonarqubecloud

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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