Heartbeat or ping on idle connections; zombie connections closed after configurable timeout
Why it matters
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.
Severity rationale
High because zombie connections from abruptly-closed clients accumulate over time, degrading server throughput and exhausting connection capacity.
Remediation
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.
Detection
-
ID:
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(); });
External references
- iso-25010:2011 · reliability.fault-tolerance — Fault Tolerance — heartbeat detects and closes zombie connections
Taxons
History
- 2026-04-18·v1.0.0·Initial import from community-realtime·automated