From c6fb4262826f83a635fb024cd8ddc0a0f5fe449b Mon Sep 17 00:00:00 2001 From: Greg Pomerantz Date: Sat, 10 Oct 2026 06:49:11 -0400 Subject: [PATCH] Add a 15s startup watchdog that dumps all goroutine stacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The splash hang is intermittent and the process is unattachable from the shell (not debuggable, no root, no signal permission), so the next occurrence must be self-reporting: if no frame renders within 15 s of entering the frame loop, log debug.Stack() — the blocked goroutine's stack is the diagnosis. --- cmd/pad/main.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/cmd/pad/main.go b/cmd/pad/main.go index dc4b43d..18d59b5 100644 --- a/cmd/pad/main.go +++ b/cmd/pad/main.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "runtime/debug" "strings" "sync" "time" @@ -156,9 +157,24 @@ func run(w *app.Window) error { var mu sync.Mutex var frame editor.Frame + firstFrameCh := make(chan struct{}) // closed when the first frame renders (watchdog) + go frameReceiver(w, &mu, &frame, logic.FrameChan()) go logic.Run() log.Printf("startup: logic running, entering frame loop") + // Startup watchdog: if no frame has been rendered 15 s after entering + // the frame loop, dump every goroutine's stack. A splash-screen hang is + // intermittent and usually gone by the time anyone can attach a + // debugger; the dump makes the next occurrence self-reporting — which + // goroutine is blocked where (FUSE read, channel, GL loop) is the whole + // diagnosis. + go func() { + select { + case <-firstFrameCh: + case <-time.After(15 * time.Second): + log.Printf("STARTUP WATCHDOG: no frame 15s after entering the frame loop — goroutine dump:\n%s", debug.Stack()) + } + }() // firstFrame logs the moment the UI actually drew something (splash // done): the anchor for bisecting startup hangs — the log line missing // after "entering frame loop" points at the blocked phase. @@ -200,6 +216,7 @@ func run(w *app.Window) error { case app.FrameEvent: if !firstFrame { firstFrame = true + close(firstFrameCh) log.Printf("startup: first frame rendered") } if perfOn {