Closed bhyzy closed 2 years ago
Apologies, this PR was meant to go to our fork for now.
@bhyzy no worries, wonderful write up. If further testing on your fork proves this as a solution then I'd be happy to take a PR to the main repo.
@dsrees Perfect, thanks, I'll be sure to follow up with a PR here then. We're also adding some logging, it will be interesting to see how widespread this issue has been.
Problem
Socket
does not correctly reconnect if the server fails to respond to a heartbeat in time, but acknowledges the subsequent client-initiated disconnection request.Root cause analysis
Every
heartbeatInterval
the socket tries to push a heartbeat message to the server (sendHeartbeat
). If a response to the previous one has not been received yet (pendingHeartbeatRef
not nil), a timeout is proclaimed. This in turn triggers an abnormal closure of the socket:abnormalClose("heartbeat timeout")
:The socket then closes the connection to the server with
CloseCode.normal
(1000):The transport layer (
self.connection
) simply drops the connection (showingURLSessionTransport
):Two things can happen now:
Server is unreachable: the disconnection request is not processed, the transport layer reports an abnormal closure to the socket (
self.delegate
):Server is reachable: the disconnection request is processed, the transport layer reports a closure event back to the socket (
self.delegate
), passing in the close code that was sent to the server (1000 =CloseCode.normal
):Both of these scenarios end up calling the
Socket.onClose
method:In scenario 1.
self.closeWasClean
is set tofalse
. In scenario 2. though, it's set totrue
, overriding thefalse
value set earlier inabnormalClose
. This in turn causes the reconnection logic to be skipped:Solution
The proposed solution introduces another state variable
closingAbnormally
to be used instead ofcloseWasClean
in the abnormal closure scenario. By keeping this information separately from the more genericcloseWasClean
state,Socket
is able to tell whether a 1000 code disconnection was caused by normal operation or an abnormal condition (heartbeat timeout), and execute the reconnection logic accordingly.