TCP connections broken by NAT timeouts, mobile network switches, or abrupt process kills do not send a FIN packet. Without a heartbeat, the server holds those socket objects open indefinitely, consuming memory and file descriptors for clients that will never respond again. Enough zombie connections degrade throughput for active users and can prevent new connections from being accepted. ISO 25010 reliability requires that the system detect and recover from failed connections rather than accumulate them silently.
High because zombie connections from abruptly-closed clients accumulate over time, degrading server throughput and exhausting connection capacity.
Implement a server-side ping loop and close any socket that fails to respond within the timeout window.
const PING_INTERVAL = 30_000; // 30 s
const PONG_TIMEOUT = 60_000; // 60 s
io.on('connection', (socket) => {
let deadline: NodeJS.Timeout;
const resetDeadline = () => {
clearTimeout(deadline);
deadline = setTimeout(() => socket.disconnect(true), PONG_TIMEOUT);
};
socket.on('pong', resetDeadline);
const ping = setInterval(() => socket.emit('ping'), PING_INTERVAL);
socket.on('disconnect', () => {
clearInterval(ping);
clearTimeout(deadline);
});
resetDeadline();
});
Socket.IO's built-in pingInterval/pingTimeout options can replace this boilerplate — set them explicitly in the Server constructor rather than relying on defaults.
ID: community-realtime.connection-management.heartbeat-timeout
Severity: high
What to look for: Count all heartbeat/keepalive mechanisms: interval-based pings, pong handlers, timeout detection, framework-level keepalive configuration. Quote the actual timeout values if found. Verify that connections that stop responding are closed.
Pass criteria: The server sends periodic pings (or the client sends pongs) at an interval of no more than 60 seconds, and connections that don't respond within a configurable timeout are closed server-side. At least 1 heartbeat mechanism must be present.
Fail criteria: No heartbeat mechanism, or connections that stop responding remain open indefinitely, creating zombie connections.
Skip (N/A) when: Never — heartbeats are essential for detecting stale connections.
Detail on fail: "No ping/heartbeat mechanism found. Zombie connections from abruptly-closed clients remain open."
Remediation: Implement heartbeat/pong detection to identify and close dead connections:
const HEARTBEAT_INTERVAL = 30000; // 30 seconds
const HEARTBEAT_TIMEOUT = 60000; // 60 seconds
io.on('connection', (socket) => {
let heartbeatTimer: NodeJS.Timeout;
const resetHeartbeat = () => {
clearTimeout(heartbeatTimer);
heartbeatTimer = setTimeout(() => {
socket.disconnect(true); // Force disconnect
}, HEARTBEAT_TIMEOUT);
};
socket.on('pong', resetHeartbeat);
const pingInterval = setInterval(() => {
socket.emit('ping');
}, HEARTBEAT_INTERVAL);
socket.on('disconnect', () => {
clearInterval(pingInterval);
clearTimeout(heartbeatTimer);
});
resetHeartbeat();
});