Skip to content

Do not abandon a connection attempt before ConnectTimeout has elapsed - #3277

Merged
mgravell merged 1 commit into
mainfrom
marc/issue-2894-connect-abort
Oct 16, 2026
Merged

mgravell merged 1 commit into
mainfrom
marc/issue-2894-connect-abort

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

Partial fix for #2894 (Linux reconnect stuck in Connecting).

Problem

PhysicalBridge.OnHeartbeat's Connecting branch aborts an in-flight attempt as soon as the retry policy says a retry is due. The default policy is ExponentialRetry(ConnectTimeout / 2), so with the default 5s connect timeout an attempt is killed after roughly 2.5s, well inside its own ConnectTimeout budget, and reported as a connect timeout.

Name resolution runs inside socket.ConnectAsync(DnsEndPoint, ct). When the primary DNS server does not answer, glibc waits ~5s per lookup before the secondary responds (Windows sidelines the dead server, which is why the report is Linux-only). Every reconnect attempt therefore died during resolution and the bridge never left Connecting. Reproduced deterministically with an LD_PRELOAD shim that delays getaddrinfo; 4s lookups never reconnected under the default policy and did under LinearRetry(10000).

Change

The heartbeat may only abort an attempt once it has been running for at least ConnectTimeout and the retry policy says a retry is due. The socket connect already carries its own ConnectTimeout cancellation; this check is what bounds TLS and the handshake after it. The elapsed-time calculation is factored into a helper shared with DueForConnectRetry.

No public API change.

Test

ConnectionAttemptUnitTests.AttemptIsNotAbandonedBeforeConnectTimeout: a listener that accepts TCP and never answers TLS, ConnectTimeout = 1500, 100ms heartbeat, LinearRetry(50). Asserts the gap between consecutive timed-out attempts is at least the connect timeout. Fails on main with "attempt abandoned after 100ms, inside the 1500ms connect timeout".

Not in this PR

A fallback to the last address an endpoint successfully connected to when resolution fails or times out, keeping the TLS host as the DNS name. That is the second half of the fix discussed on the issue and will follow separately.

…#2894)

The heartbeat's Connecting branch aborted an in-flight attempt as soon as the
retry policy said a retry was due. With the default ExponentialRetry at half
the connect timeout that is ~2.5s, so any attempt whose name resolution or TLS
handshake took longer than that was killed inside its own ConnectTimeout budget
and reported as a connect timeout. A primary DNS server that does not answer
costs ~5s per lookup on glibc, so on Linux every reconnect attempt died during
resolution and the connection never left Connecting.

Require ConnectTimeout to have elapsed before the retry policy may abort an
attempt. The socket connect already carries its own ConnectTimeout cancellation;
this check bounds TLS and the handshake after it.

@mgravell mgravell left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@mgravell
mgravell merged commit 5625693 into main Oct 16, 2026
6 of 7 checks passed
@mgravell
mgravell deleted the marc/issue-2894-connect-abort branch October 16, 2026 08:08
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