Skip to content

Commit 9c69953

Browse files
authored
fix: reduce dev server timeouts (#110)
1 parent 8dfd3ca commit 9c69953

1 file changed

Lines changed: 22 additions & 6 deletions

File tree

src/rockgarden/server/handler.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
from importlib.resources import files
66
from pathlib import Path
77

8+
# Interval for SSE heartbeat comments. Browsers cap concurrent HTTP/1.1
9+
# connections per origin (~6), and an SSE connection holds one slot. If stale
10+
# connections aren't detected promptly, rapid navigation can queue requests
11+
# behind them for tens of seconds. Heartbeats surface a broken pipe on write,
12+
# so stale clients are dropped quickly.
13+
SSE_HEARTBEAT_SECONDS = 10.0
14+
815
_RELOAD_JS = files("rockgarden.server").joinpath("_live_reload.js").read_text("utf-8")
916
_RELOAD_SCRIPT = f"\n<script>{_RELOAD_JS}</script>\n"
1017
_RELOAD_SCRIPT_BYTES = _RELOAD_SCRIPT.encode("utf-8")
@@ -142,15 +149,24 @@ def _handle_sse(self) -> None:
142149
self.end_headers()
143150

144151
sse_clients.add(self)
152+
# Short socket timeout so read(1) returns periodically, letting us
153+
# send heartbeat comments. A failed heartbeat write means the
154+
# client is gone and this connection can release its slot.
155+
self.connection.settimeout(SSE_HEARTBEAT_SECONDS)
145156
try:
146-
# Keep connection open until client disconnects
147157
while True:
148-
# Block on read — will raise when client disconnects
149-
data = self.rfile.read(1)
150-
if not data:
158+
try:
159+
data = self.rfile.read(1)
160+
if not data:
161+
break
162+
except TimeoutError:
163+
try:
164+
self.wfile.write(b": keepalive\n\n")
165+
self.wfile.flush()
166+
except Exception:
167+
break
168+
except Exception:
151169
break
152-
except Exception:
153-
pass
154170
finally:
155171
sse_clients.remove(self)
156172

0 commit comments

Comments
 (0)