Skip to content

fix: report the close frame as the disconnect reason - #92

Open
nmbrone wants to merge 1 commit into
mainfrom
fix/close-reason
Open

fix: report the close frame as the disconnect reason#92
nmbrone wants to merge 1 commit into
mainfrom
fix/close-reason

Conversation

@nmbrone

@nmbrone nmbrone commented Sep 3, 2026

Copy link
Copy Markdown
Owner

Three connected changes to the close handshake.

The close code was unreachable. The handshake is handled internally, so the code and reason the server sent were dropped — handle_disconnect/3 only ever saw the transport error from the socket shutting down afterwards. Application close codes (1008, 1011, the 4000 range) never reached the client, which is exactly what it needs to decide whether reconnecting makes sense. send_close/2 now remembers the frame and reports it as the reason, falling back to the Mint error when there was no handshake; close/1 clears it so a code can't leak into the next connection's disconnect.

The handshake timeout is now configurable via :close_timeout on start_link/3 (default 5000), which also makes it testable — the new test suspends the server process so our close frame is never read and the socket never closes, which is the situation that timeout exists for.

A second handshake orphaned the first timer. send_close/2 overwrote close_timer without cancelling it, and close/1 only cancels the timer it can see, so the stale one fired after the connection had been re-established and tore it down. That path is also what made the timeout's reason ambiguous, so it belongs with the other two.

The frame is reported the same way whichever side initiated the close: per the protocol the responding endpoint echoes the status code, so the code converges anyway, and for a local close the echo is never decoded (send_close/2 has already nil'd the websocket).

handle_disconnect/3 now gets {:close, code, reason} where it previously got %Mint.TransportError{reason: :closed}. Not flagged as breaking: the reason is typed term() and was undocumented, and the old value was the same for a graceful close and for a socket that simply vanished, so there was nothing dependable to match on. Two existing tests updated.

@nmbrone nmbrone changed the title fix!: report the close frame as the disconnect reason fix: report the close frame as the disconnect reason Sep 3, 2026
@nmbrone
nmbrone force-pushed the fix/close-reason branch 5 times, most recently from b877353 to 06f3a93 Compare September 3, 2026 17:47
The close handshake is handled internally, so the code and reason the server
sent were dropped: `handle_disconnect/3` only ever saw the transport error from
the socket shutting down afterwards, leaving application close codes such as
1008 or the 4000 range unreachable.

Remember the frame when the handshake starts and report it instead, falling
back to the Mint error when there was no handshake. The frame is cleared on
close so a code cannot leak into the next connection's disconnect.

Add a `:close_timeout` option to make the handshake timeout configurable, and
cover it with a test that suspends the server so the handshake goes unanswered.

`handle_disconnect/3` now receives `{:close, code, reason}` where it previously
received `%Mint.TransportError{reason: :closed}` for a connection closed through
the handshake by either side.

fix: cancel the previous close timer when starting a handshake

`send_close/2` overwrote `close_timer` without cancelling it, and `close/1` only
cancels the timer it can see. Two handshakes inside the timeout window left the
first timer armed, so it fired after the connection had been closed and
re-established, and tore down the healthy connection.
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