Skip to content

Race connection attempts across resolved addresses for the Classic client (RFC 8305) - #936

Open
arturobernalg wants to merge 1 commit into
apache:masterfrom
arturobernalg:happy-eyeballs-classic
Open

arturobernalg wants to merge 1 commit into
apache:masterfrom
arturobernalg:happy-eyeballs-classic

Conversation

@arturobernalg

@arturobernalg arturobernalg commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Implements RFC 8305 (Happy Eyeballs v2) connection racing for the Classic (blocking) client, as the third and final PR of this series, alongside #935 (Async connection racing) and arturobernalg#20 (Async independent A/AAAA resolution, stacked on #935 pending its merge). This PR is independent of the other two: the Classic client has its own connection path (java.net.Socket, not the reactor), so it needed its own, smaller mechanism rather than reusing the Async internals.

Dependency

None. Based directly on upstream master, independent of #935 and arturobernalg#20.

…ient

Replaces the one-thread-per-address, sleep-based stagger with a single
dispatcher that starts the first address immediately and waits up to
the connection attempt delay for an outstanding attempt to finish
before starting the next; on a failure it advances immediately, at the
RFC 8305 minimum spacing (10ms), instead of waiting out the full delay.
No worker thread ever sleeps a delay itself, bounding concurrency to
the number of resolved addresses actually in flight.

Socket registration goes through a small atomic registry instead of a
bare reference: deciding a winner and a worker registering its socket
can happen at the same instant, and whichever runs second closes the
socket, so nothing registered around that moment survives open
regardless of timing. shutdownNow() alone cannot abort a blocking
Socket#connect; closing the socket is what actually does that, on
every exit path (winner found, every address failed, or interrupted)
via one cleanup call in a finally block.

An interruption during the minimum-spacing wait is now propagated
instead of swallowed, aborting the race immediately like interruption
anywhere else in it. Racing is skipped in favor of the existing
sequential path when a specific, non-ephemeral local port is
requested, since binding several attempts to it concurrently would
trade a real connect failure for a spurious bind conflict.

Public, blocking connect() behavior, TLS, SOCKS and local binding are
otherwise unchanged.
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