From 2be7c34f712802d81d3fe03c0563f46d2fc52a6b Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Tue, 24 Feb 2026 05:38:21 +0900 Subject: [PATCH] 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 --- pkg/miniapp/miniapp.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/miniapp/miniapp.go b/pkg/miniapp/miniapp.go index 0c33b1087..ee1ea2323 100644 --- a/pkg/miniapp/miniapp.go +++ b/pkg/miniapp/miniapp.go @@ -210,6 +210,11 @@ type Handler struct { const maxWSClients = 4 +const ( + wsPongWait = 60 * time.Second + wsPingPeriod = 54 * time.Second // must be less than wsPongWait +) + type wsClient struct { conn *websocket.Conn } @@ -936,6 +941,13 @@ func (h *Handler) wsLogs(w http.ResponseWriter, r *http.Request) { sub := logger.Subscribe(filter) 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 initial := logger.RecentLogs(minLevel, component, 50) 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 { select { 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 { return } + case <-ticker.C: + if err := conn.WriteMessage(websocket.PingMessage, nil); err != nil { + return + } case <-done: return }