package main // shutdown_notify.go — MAJ-4 補丁 // // 對應 TDD v2/server-lifecycle.md §2.3(Minor 4)與 v2/web-ui-offline-overlay.md §3.2a。 // // 在 Wails OnBeforeClose / Restart 實際對 server 下 SIGTERM 之前,先打一個 POST 到 // server 的 /api/system/shutdown-notify,讓 server 透過 /ws/system WebSocket 廣播 // server:shutdown-imminent 給所有已連線的瀏覽器 tab,觸發 Offline Overlay 立即顯示。 // // 設計原則:best-effort,不阻塞 shutdown 流程。 // - 1 秒 timeout:server 沒起來、卡住、network error 都視為失敗,不重試 // - 失敗完全忽略(log 也不寫,避免 shutdown 期間噴 stderr) // - port <= 0 時直接 return(server 根本沒起來) import ( "context" "fmt" "net/http" "time" ) // notifyShutdownImminentTimeout 控制 HTTP request 的總 timeout。 // server 端 ShutdownNotify handler 會 sleep 100 ms 後才回,留 900 ms buffer 給網路與 scheduler。 // 變數化以便測試注入。 var notifyShutdownImminentTimeout = 1 * time.Second // notifyShutdownImminent 通知 server 廣播 shutdown-imminent 給所有 WebSocket client。 // // 參數: // // ctx — caller context(會被 timeout 包裹,避免 ctx 卡住整個 shutdown) // port — server 正在聽的 port;<= 0 代表 server 還沒起來,直接 skip // reason — "quit" 或 "restart",對應 Offline Overlay 的 reason 映射 // // 錯誤容忍:全部錯誤靜默處理,絕不阻塞呼叫端。 func notifyShutdownImminent(ctx context.Context, port int, reason string) { if port <= 0 { return } if ctx == nil { ctx = context.Background() } reqCtx, cancel := context.WithTimeout(ctx, notifyShutdownImminentTimeout) defer cancel() url := fmt.Sprintf("http://127.0.0.1:%d/api/system/shutdown-notify?reason=%s", port, reason) req, err := http.NewRequestWithContext(reqCtx, http.MethodPost, url, nil) if err != nil { return } resp, err := http.DefaultClient.Do(req) if err != nil { return } // 不需讀 body,關閉即可(釋放底層連線) _ = resp.Body.Close() }