From 1a3ef572d1c920b74ab3a4992a4ba133e408b6ba Mon Sep 17 00:00:00 2001 From: kami Date: Sat, 4 Jul 2026 00:08:56 +0400 Subject: [PATCH] mavweb: wait for core socket at startup instead of crash-looping mavweb log.Fatal'd if mavend's socket wasn't up yet, so under compose it crash-looped (relying on restart:unless-stopped) until core finished booting its models. depends_on only orders container start, not socket readiness. dialCoreWithRetry polls with capped backoff up to 60s; still fatal past the deadline. Mid-life core restarts remain covered by ipc.Client's redial-on-drop. Co-Authored-By: Claude Opus 4.8 --- cmd/mavweb/main.go | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/cmd/mavweb/main.go b/cmd/mavweb/main.go index e55f2ea..81bdba9 100644 --- a/cmd/mavweb/main.go +++ b/cmd/mavweb/main.go @@ -80,7 +80,7 @@ func main() { var core ipc.CoreAPI if *coreSock != "" { - c, err := ipc.Dial(*coreSock) + c, err := dialCoreWithRetry(*coreSock, 60*time.Second) if err != nil { log.Fatalf("dial core %s: %v", *coreSock, err) } @@ -162,6 +162,36 @@ func main() { } } +// dialCoreWithRetry waits for core's IPC socket to appear before giving up. +// Core loads models on boot and (esp. under compose) may come up after mavweb; +// depends_on only orders container start, not socket readiness. Retrying with +// capped backoff up to timeout stops mavweb crash-looping on a cold start. A +// core still absent past the deadline is genuinely fatal. Mid-life core +// restarts are handled separately by ipc.Client's redial-on-drop. +func dialCoreWithRetry(path string, timeout time.Duration) (*ipc.Client, error) { + deadline := time.Now().Add(timeout) + delay := 200 * time.Millisecond + for attempt := 1; ; attempt++ { + c, err := ipc.Dial(path) + if err == nil { + if attempt > 1 { + log.Printf("core socket %s ready after %d attempts", path, attempt) + } + return c, nil + } + if time.Now().After(deadline) { + return nil, err + } + if attempt == 1 { + log.Printf("waiting for core socket %s ...", path) + } + time.Sleep(delay) + if delay < 2*time.Second { + delay *= 2 + } + } +} + func handleWS(w http.ResponseWriter, r *http.Request, voiceAddr string) { conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ OriginPatterns: []string{"*"},