fix: add WebSocket ping/pong to detect dead connections in log viewer
Without ping/pong, silently dropped connections (network loss, phone sleep) would leak goroutines and logger subscriptions until the OS-level TCP timeout (minutes). Now pings every 54s; if no pong within 60s the connection is closed and all resources (goroutines, subscriptions, client slots) are released. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
26481bc003
commit
2be7c34f71
1 changed files with 20 additions and 1 deletions
|
|
@ -210,6 +210,11 @@ type Handler struct {
|
||||||
|
|
||||||
const maxWSClients = 4
|
const maxWSClients = 4
|
||||||
|
|
||||||
|
const (
|
||||||
|
wsPongWait = 60 * time.Second
|
||||||
|
wsPingPeriod = 54 * time.Second // must be less than wsPongWait
|
||||||
|
)
|
||||||
|
|
||||||
type wsClient struct {
|
type wsClient struct {
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
}
|
}
|
||||||
|
|
@ -936,6 +941,13 @@ func (h *Handler) wsLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
sub := logger.Subscribe(filter)
|
sub := logger.Subscribe(filter)
|
||||||
defer logger.Unsubscribe(sub)
|
defer logger.Unsubscribe(sub)
|
||||||
|
|
||||||
|
// Configure ping/pong to detect dead connections
|
||||||
|
conn.SetReadDeadline(time.Now().Add(wsPongWait))
|
||||||
|
conn.SetPongHandler(func(string) error {
|
||||||
|
conn.SetReadDeadline(time.Now().Add(wsPongWait))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
// Send initial data
|
// Send initial data
|
||||||
initial := logger.RecentLogs(minLevel, component, 50)
|
initial := logger.RecentLogs(minLevel, component, 50)
|
||||||
if err := conn.WriteJSON(map[string]any{"type": "init", "entries": initial}); err != nil {
|
if err := conn.WriteJSON(map[string]any{"type": "init", "entries": initial}); err != nil {
|
||||||
|
|
@ -953,7 +965,10 @@ func (h *Handler) wsLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Stream loop
|
// Stream loop with periodic pings
|
||||||
|
ticker := time.NewTicker(wsPingPeriod)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case entry, ok := <-sub.Ch:
|
case entry, ok := <-sub.Ch:
|
||||||
|
|
@ -965,6 +980,10 @@ func (h *Handler) wsLogs(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := conn.WriteJSON(map[string]any{"type": "entry", "entry": entry}); err != nil {
|
if err := conn.WriteJSON(map[string]any{"type": "entry", "entry": entry}); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
case <-done:
|
case <-done:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue