From c5c480091bb9e1c3a11c1891c5f1d39c0e4720fe Mon Sep 17 00:00:00 2001 From: xiaohai1314-sys <219774556+xiaohai1314-sys@users.noreply.github.com> Date: Wed, 11 Nov 2026 01:30:17 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20WAA=20VM=20=E5=90=8E=E5=8F=B0?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E8=B5=B0=20paniclog=EF=BC=8Cpanic=20?= =?UTF-8?q?=E6=97=B6=E4=BB=8D=E6=B8=85=E7=90=86=20refreshing=20=E5=B9=B6?= =?UTF-8?q?=E5=85=B3=E9=97=AD=20done?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refreshVM 的后台 goroutine 原为裸 go func(),close(refresh.done) 不在 defer 中: nextVM 或换装新 VM 时 panic 会带崩进程,等待 proof 的调用方也会挂到 ctx 超时。 改为 paniclog.Go,收尾移入 defer,panic 时写入错误、清除 refreshing(仅当仍是本次重建)、 关闭 done 后再交给 paniclog 记录。顺带把 Linux 托盘 DBus 信号监听也改走 paniclog。 --- internal/aistudio/runtime_go.go | 29 +++++++++++++++++++++++------ internal/desktop/tray_linux.go | 4 ++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/internal/aistudio/runtime_go.go b/internal/aistudio/runtime_go.go index 9f10003b..e6623ce5 100644 --- a/internal/aistudio/runtime_go.go +++ b/internal/aistudio/runtime_go.go @@ -931,9 +931,29 @@ func (runtime *goWAARuntime) refreshVM() *vmRefresh { refresh := &vmRefresh{done: make(chan struct{}), cancel: cancel} runtime.refreshing = refresh previous := runtime.vm - go func() { + paniclog.Go("runtime_go.go:refreshVM", func() { + var err error + // 收尾放进 defer:nextVM 或换装新 VM 时 panic 也要清掉 refreshing 并关闭 done, + // 否则等 proof 的调用方只能挂到 ctx 超时,之后的刷新也会一直复用这次失效的重建 + defer func() { + recovered := recover() + if recovered != nil { + err = fmt.Errorf("WAA SpiderMonkey VM 刷新 panic: %v", recovered) + } + runtime.vmMu.Lock() + refresh.err = err + if runtime.refreshing == refresh { + runtime.refreshing = nil + } + runtime.vmMu.Unlock() + close(refresh.done) + if recovered != nil { + panic(recovered) + } + }() defer cancel() - vm, err := runtime.nextVM(ctx, previous) + vm, nextErr := runtime.nextVM(ctx, previous) + err = nextErr runtime.vmMu.Lock() defer runtime.vmMu.Unlock() switch { @@ -949,10 +969,7 @@ func (runtime *goWAARuntime) refreshVM() *vmRefresh { runtime.vm = vm runtime.scheduleRefresh() } - refresh.err = err - runtime.refreshing = nil - close(refresh.done) - }() + }) return refresh } diff --git a/internal/desktop/tray_linux.go b/internal/desktop/tray_linux.go index 6e26d3ce..3327f743 100644 --- a/internal/desktop/tray_linux.go +++ b/internal/desktop/tray_linux.go @@ -40,7 +40,7 @@ func (s *App) startTray() { slog.Info("桌面环境没有系统托盘,关闭窗口即退出") signals := make(chan *dbus.Signal, 10) conn.Signal(signals) - go func() { + paniclog.Go("tray_linux.go:watchTray", func() { defer conn.RemoveSignal(signals) for signal := range signals { if signal.Name == "org.freedesktop.DBus.NameOwnerChanged" && len(signal.Body) == 3 && signal.Body[0] == statusNotifierWatcher && signal.Body[2] != "" { @@ -48,7 +48,7 @@ func (s *App) startTray() { return } } - }() + }) } // bindTray 在 Wails 注册的托盘项上接收 StatusNotifierItem 的点击与上下文菜单事件 From 98b66c0fa917becdf3deecc5bbe05f5d63e12b38 Mon Sep 17 00:00:00 2001 From: xiaohai1314-sys <219774556+xiaohai1314-sys@users.noreply.github.com> Date: Wed, 11 Nov 2026 01:36:52 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20runtime=5Fgo.go=20=E7=9A=84=20time.A?= =?UTF-8?q?fterFunc=20=E5=9B=9E=E8=B0=83=EF=BC=88VM=20=E5=88=B0=E6=9C=9F?= =?UTF-8?q?=E5=88=B7=E6=96=B0=E3=80=81incidents=20=E8=BD=AE=E8=AF=A2?= =?UTF-8?q?=E3=80=81followUp=20=E8=AF=B7=E6=B1=82=EF=BC=89=E5=8A=A0=20pani?= =?UTF-8?q?clog.Recover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/aistudio/runtime_go.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/aistudio/runtime_go.go b/internal/aistudio/runtime_go.go index e6623ce5..d6dda03d 100644 --- a/internal/aistudio/runtime_go.go +++ b/internal/aistudio/runtime_go.go @@ -378,6 +378,7 @@ func (runtime *goWAARuntime) open(ctx context.Context, fingerprint map[string]an // scheduleIncidents 在 at 时刻以官网页面的请求头发出 ListIncidentsHistory,并按 incidentsPollInterval 安排下一次,直到 runtime 关闭 func (runtime *goWAARuntime) scheduleIncidents(at time.Time, apiKey string, visitID string, extension string) { runtime.incidentsTimer = time.AfterFunc(time.Until(at), func() { + defer paniclog.Recover("runtime_go.go:scheduleIncidents") runtime.vmMu.Lock() if runtime.closed { runtime.vmMu.Unlock() @@ -396,6 +397,7 @@ func (runtime *goWAARuntime) scheduleRefresh() { runtime.refreshTimer.Stop() } runtime.refreshTimer = time.AfterFunc(waaLifetime, func() { + defer paniclog.Recover("runtime_go.go:scheduleRefresh") runtime.vmMu.Lock() defer runtime.vmMu.Unlock() if !runtime.closed && !time.Now().Before(runtime.vmExpires) { @@ -1072,6 +1074,7 @@ func (runtime *goWAARuntime) SendProtected(ctx context.Context, rawURL string, h companion := request.Clone() companion.Del("Priority") time.AfterFunc(followUp.Delay, func() { + defer paniclog.Recover("runtime_go.go:followUp") runtime.drain(runtime.do(context.WithoutCancel(ctx), http.MethodPost, followUp.URL, followUp.Body, companion)) }) }