Skip to content

socket: add timeout to websocket dial - #3

Open
ADAMAYAMEOGO wants to merge 1 commit into
devlikeapro:fork-main-2026-10-13from
ADAMAYAMEOGO:websocket-dial-timeout
Open

ADAMAYAMEOGO wants to merge 1 commit into
devlikeapro:fork-main-2026-10-13from
ADAMAYAMEOGO:websocket-dial-timeout

Conversation

@ADAMAYAMEOGO

Copy link
Copy Markdown

Refs devlikeapro/waha#1043.

FrameSocket.Connect dials with the context passed to Connect (BackgroundEventCtx, no deadline), and neither net/http nor coder/websocket limit the wait for the upgrade response. A proxy or middlebox that accepts the connection but never answers the upgrade request blocks Connect forever while it holds socketLock: the reconnect loop never tries again and Disconnect() blocks too. In WAHA this is a GOWS session stuck in STARTING whose restart/stop end in Maximum execution time is exceeded until the container is restarted.

This adds socket.DialTimeout (20 s by default, like NoiseHandshakeResponseTimeout, 0 disables it). Only the dial gets the timeout; the established connection keeps the parent context, as coder/websocket already does for HTTPClient.Timeout. A timed out dial returns ErrDialFailed, so it is retried like any other network failure.

Tests: a server that never answers the upgrade (fails without the timeout), and a websocket that only sends after 3× the timeout (the connection must survive). gofmt, go vet and go test -race -count=10 ./socket/ pass on fork-main-2026-10-13. (The repository-wide staticcheck hook currently reports getButtonTypeFromMessage and getButtonAttributes as unused in send.go, on the base branch as well.)

We reproduced the hang with a CONNECT proxy that stalls after TLS: before, POST /restart returned 500 after 30 s and the session never retried; with this change, 201 after 23.5 s, a retry every 20 s, and WORKING again 28 s after the proxy recovered. We have run GOWS with it in production since 1 October.

Based on fork-main-2026-10-13. The same commit applies to tulir/whatsmeow main.

The dial used the context passed to Connect, which normally has no
deadline, and neither net/http nor coder/websocket limit how long to wait
for the upgrade response. A proxy or middlebox that accepts the connection
but never answers the upgrade request made Connect block forever while
holding the socket lock, so Disconnect blocked too and the client could
only be recovered by restarting the process.

Limit the dial to DialTimeout (20 seconds by default, zero disables it).
The timeout only covers the dial, the established connection still uses
the parent context. A timed out dial returns ErrDialFailed, so it is
retried like any other connection failure.
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