LEARNING OBJECTIVES โต
- Understand how
EventSourcehandles different HTTP status codes (200,204,301,401,404,500). - Differentiate between transient network interruptions (auto-retry) and fatal server errors (permanent closure).
- Diagnose the "Zombie Connection" anomaly where TCP connections silently freeze without triggering
onerror. - Construct a client-side Heartbeat Watchdog timer to automatically detect and revive stalled streams.
- Implement user-facing connection health indicators (Connected, Reconnecting, Stalled, Offline).
๐ The Mental Model & Story (Intuitive Foundation)
Imagine a deep-sea diver tethered to a surface ship via an air and communication umbilical cable.
SURFACE SHIP (Server)
|
| === Umbilical Cable (SSE Stream) ===
|
v
DEEP-SEA DIVER (Browser Client)
There are three ways communication can fail:
- The Official Recall Order (
HTTP 204 No Content): The ship radio operator says: "Mission complete, pack up." The diver closes the line and returns to surface (readyState: CLOSED). - A Sudden Wave Pulls the Cable (Transient Network Drop): The line pulls tight and pops off the diver's helmet radio. The radio beeps (
onerror). The diver doesn't panicโthe radio automatically scans and reconnects within 3 seconds (readyState: CONNECTING). - The Silent Ice-Over (The "Zombie Connection"): A chunk of Arctic ice silently freezes the radio antenna. The ship is still broadcasting, and the diver's radio still displays a green light ("Connected"), but zero voice packets are getting through.
If the diver just waits indefinitely looking at the green light, they will never know the line is dead.
The Solution? The surface ship taps the line every 10 seconds: "Beep... Beep... Beep..." (Heartbeat Ping). The diver wears a Watchdog Stopwatch. If 25 seconds pass without hearing a "Beep", the diver immediately knows the line is frozen, cuts the dead cable, and fires a fresh backup line (new EventSource()).
Technical Deep Dive & Specifications
HTTP Status Code Handling in WHATWG Specification
When an EventSource initializes or reconnects, the browser's network layer inspects the HTTP response code before dispatching to JavaScript:
+-----------------------------------------------------------------------------------------------+
| HTTP STATUS CODE RESPONSE MATRIX |
+-----------------------------------------------------------------------------------------------+
| Status Code | Browser Reaction | Next readyState | Reconnection? |
+-----------------------+-----------------------------------+:---------------:+:---------------:|
| 200 OK | Verifies text/event-stream; opens | 1 (OPEN) | N/A (Active) |
| 204 No Content | Clean termination | 2 (CLOSED) | NO (Permanent) |
| 301 / 307 Redirect | Follows redirect transparently | 0 (CONNECTING) | YES |
| 401 / 403 Forbidden | Fires onerror, halts | 2 (CLOSED) | NO (Fatal) |
| 404 Not Found | Fires onerror, halts | 2 (CLOSED) | NO (Fatal) |
| 500 / 502 / 503 | Fires onerror, retries backoff | 0 (CONNECTING) | YES (Transient) |
| Network Drop / RST | Fires onerror, retries backoff | 0 (CONNECTING) | YES (Transient) |
+-----------------------------------------------------------------------------------------------+
The Silent "Zombie" TCP Connection Problem
In modern mobile and cloud networking, connections pass through Network Address Translation (NAT) gateways, mobile cell towers, and cloud load balancers.
If a mobile phone enters a subway tunnel or a router drops a TCP table without sending a TCP FIN or RST packet, the browser's operating system socket will remain in ESTABLISHED state for up to 2 hours (due to default OS TCP Keep-Alive timers).
To client JavaScript:
eventSource.readyState === 1(OPEN)- No
onerrorevent has fired. - Yet no real-time events can ever arrive!
The Heartbeat Watchdog Pattern
To solve zombie connections, we implement a bi-part contract:
- Server Contract: Emits a heartbeat comment (
: ping\n\n) or event (event: ping\n\n) at interval $T$ (e.g. 15 seconds). - Client Watchdog: Starts a timer for $2.5 \times T$ (e.g. 35 seconds). Every time any message or comment arrives, the timer resets. If the timer ever expires, the stream is confirmed to be a zombie. The client immediately invokes
.close()and reconnects.
SERVER PING: --- [Ping: 0s] -------- [Ping: 15s] -------- [Ping: 30s] ------------------ [SILENCE] ---->
CLIENT TIMER: [Reset: 35s] -------- [Reset: 35s] -------- [Reset: 35s] ----------------- [EXPIRES AT 65s!]
|
v
[FORCE RECONNECT STREAM]
๐ป Interactive Code Playground
Below is a complete interactive Connection Health Monitor & Zombie Watchdog Simulator. You can simulate active streaming, transient network drops, fatal server 404 errors, and silent zombie freezes.
Starter Code
Line-by-Line Code Breakdown
- Lines 105โ109: Defines a 5000ms watchdog threshold for demonstration purposes (in production, typically 30โ45s).
- Lines 125โ144 (
resetWatchdog): Called every time any packet (data or ping) arrives. It resets the countdown bar back to 100%. - Lines 146โ158 (
onWatchdogTimeout): If 5 seconds elapse without receiving a packet, the watchdog fires. Even though the browser still thought the connection was open, the watchdog catches the stall, terminates the dead socket, and initializes a fresh connection. - Lines 185โ192 (
btnFatal404): Demonstrates that for4xxerrors, the browser transitions toCLOSED(2) permanently without retrying.
Expected Browser Render Output
๐ก๏ธ Connection State & Watchdog Monitor
Status: [ Connection: 1: OPEN (Healthy) ] [ Watchdog: Active ]
Watchdog Expiry Progress: [ ============================== 100% ]
Controls:
[ Emit Event / Ping ] [ Simulate Transient Drop ] [ Simulate Zombie Freeze ] [ Simulate 404 Fatal ]
Log Output:
[10:45:00] Initialized EventSource connection to /api/stream
[10:45:03] ๐ฅ Received [ping] heartbeat from server. Connection verified.
[10:45:06] ๐ง SIMULATING ZOMBIE STATE: Server silenced. Socket remains "OPEN". Watchdog counting down...
[10:45:11] ๐จ WATCHDOG TIMEOUT EXPIRED! Zombie connection detected.
[10:45:12] ๐ Watchdog closed dead socket and spawned fresh EventSource.๐๏ธ Hands-On Exercise
๐ฏ The Challenge: Build a Production WatchdogEventSource
Instructions:
- Create a class
WatchdogEventSourcethat wrapsEventSource. - Accept options
{ timeoutMs: 30000, onStateChange: (state) => {} }. - Reset internal timers on any incoming message or custom event.
- If timeout expires, forcefully invoke
.close(), notifyonStateChange('ZOMBIE_RECONNECTING'), and instantiate a newEventSource.
๐ Starter Code Sandbox
โ ๏ธ Common Pitfalls
- Not Checking
readyStateinonerror: Many developers assumeonerroralways means the connection is permanently dead. In reality,onerrorfires during normal transient network retries (wherereadyState === 0). Only tear down UI state ifreadyState === 2(CLOSED). - Forgetting to Clear Watchdog Timers on
.close(): If you invokeeventSource.close()but forget to runclearTimeout(watchdogTimer), the watchdog will fire in the background and accidentally spawn a new connection. - Setting Watchdog Timeout Shorter than Server Heartbeat: If your server sends a heartbeat every 20 seconds, but your client watchdog timeout is set to 15 seconds, the client will endlessly kill healthy connections every 15 seconds! Always configure client watchdog to at least $2\times$ or $2.5\times$ the server heartbeat frequency.
๐ก Pro Tips
- Handling Mobile Background Throttling: On iOS Safari and Android Chrome, JavaScript timers are throttled when a tab goes into the background. Use the
document.addEventListener('visibilitychange', ...)event to immediately refresh the watchdog and check stream health when the user returns to the tab. - Server-Side Graceful Shutdowns: When deploying new backend container versions (e.g. in Kubernetes), send an
event: shutdownevent withretry: 1000to all active clients before terminating the pod. This signals clients to reconnect smoothly to a new replica.
๐ Key Takeaways
200 OKopens the stream;204 No Contentcloses it cleanly without retries;4xxcodes are fatal;5xxand network drops trigger automatic reconnection.- Silent zombie TCP connections occur when network gateways drop state without sending FIN/RST packets.
- A server heartbeat combined with a client-side Heartbeat Watchdog timer reliably detects and revives zombie streams.
- Always configure client watchdog timeouts to $2\times$ to $2.5\times$ the server heartbeat interval.
- Check
readyStateinsideonerrorto distinguish between temporary reconnection and permanent stream failure. - --