Skip to content

A cancelled dial stops instead of connecting to the next address - #113

Merged
sepehr-safari merged 2 commits into
mainfrom
a-cancelled-dial-stops
Sep 23, 2026
Merged

sepehr-safari merged 2 commits into
mainfrom
a-cancelled-dial-stops

Conversation

@sepehr-safari

Copy link
Copy Markdown
Contributor

Closes #112.

dial takes no deadline, so the way to bound it is to run it with io.concurrent and cancel it. Two things stopped that from working against a relay whose host never answers the SYN:

  • The connect loop caught every error, Canceled included, and moved on to the next resolved address. Once a task has been cancelled, its later I/O can no longer be interrupted, so that next connect ran to its own timeout. Canceled now returns at once.
  • getaddrinfo was called without hints and listed every address once per socket type, so each address was tried twice. It is now asked for stream sockets only.

The dial documentation now says how to bound it, and that the name lookup is the one step a cancel cannot interrupt, since it is a plain libc call.

Verified

a dial waiting on a silent peer can be cancelled, and frees what it made runs in CI. It dials a local listener that never answers the upgrade, cancels after 100 ms, and requires the cancel to land promptly with nothing leaked.

The SYN case needs a listener whose backlog is full so the kernel drops the SYN, which is not something to do in CI. Driven by hand against that, with a 2 second deadline, under a leak-checking allocator:

Before After
time for the cancel to land 15,286 ms, dial returned Timeout 0 ms, dial returned Canceled
sockets left open none none
leaks none none

A peer that accepts and stays silent cancels in 0 ms on both builds, which the new test pins.

All 211 tests pass.

Release

The last commit bumps the version to 0.14.4, carrying this and #111.

The connect loop caught every error, Canceled included, and moved on to the next resolved address, whose connect the cancellation could no longer interrupt. A caller bounding dial by cancelling it still waited out the TCP connect timeout against a host that never answers the SYN. Canceled now returns at once.

getaddrinfo is also asked for stream sockets only. Without hints it listed every address once per socket type, so each was tried twice.

The dial documentation says how to bound it, and a test pins that a dial waiting on a silent peer can be cancelled and frees what it allocated.

Closes #112.
Carries #111 and the fix on this branch: a dial can be abandoned cleanly, whether it is cancelled while connecting, fails after the TLS handshake, or is cancelled while waiting for the websocket upgrade.
@sepehr-safari
sepehr-safari merged commit dc11560 into main Sep 23, 2026
2 checks passed
@sepehr-safari
sepehr-safari deleted the a-cancelled-dial-stops branch September 23, 2026 09:11
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.

A cancelled dial goes on to connect to the next address

1 participant