← Notes

Answer the Ping on the Socket That Received It

2 min read#WebSocket#Android

The Android client opened 159 WebSocket connections in a day, with 160 abnormal closes. The main cause was retransmission on the 5 GHz link; three small bugs in the code turned every drop and every return to the foreground into one more zombie connection.

First: a forced reconnect only closed the old connection when the address had changed; with the same address it just opened a new one and left the old one in place. Second: for a superseded connection, the client returned early in the first line of every callback; it ignored the connection rather than closing it. Pings the server sent on the old connection therefore went unanswered, and it could only be kicked out by the server’s heartbeat timeout. The pong reply had a separate small race: it used the “current connection” reference, so if a reconnect happened between the check and the send, the pong went out on the new connection. Third: every return to the foreground forced a reconnect unconditionally.

The server had its half too: removing a connection from the registry deleted the record without closing the socket, leaving half-open connections the server couldn’t see and the client still thought were live.

Each fix is small: close the old connection before building a new one; close any connection that gets superseded; send the pong on the connection that received the ping; if already connected, only resync state instead of reconnecting; and after the server deregisters a connection, close it outside the lock.