fix: 补 panic 兜底,防止将来任何内部错误导致整进程消失
上一提交修了并发 map 写的问题(fatal error,不可 recover)。 这次给引擎加一层普通 panic 的兜底: - safeRunTask:画廊级别,panic 时把该画廊标记失败 + 记日志 - safeDownloadPage:页级别,一页出错不带走整个 worker - 恢复代码本身再包一层 recover —— 万一 panic 根因就是 store 出问题, 恢复逻辑不该二次崩溃逃逸出去 这不能替代修 bug(runtime fatal error 依然拦不住), 但能保证「将来再出别的内部错误」时用户能看到日志, 而不是应用无声消失、已下载的文件也白搭。 TestSafeRunTaskRecoversPanic 验证 recover 生效 (该测试在写第一版时还真抓出了恢复代码的二次 panic 隐患)。
This commit is contained in:
@@ -160,6 +160,63 @@ func TestConcurrentPageEventsNoCrash(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// panic 兜底:将来若再有别处的 bug 导致 panic,
|
||||
// 不该让整个应用无声消失,至少要留下错误记录。
|
||||
func TestSafeRunTaskRecoversPanic(t *testing.T) {
|
||||
s, _ := newTestStore(t)
|
||||
sink := &panicSink{}
|
||||
e := NewEngine(s, sink, s.Settings())
|
||||
|
||||
// 故意把 store 置 nil,让 runTask 内部第一处解引用就 panic。
|
||||
// safeRunTask 必须吃掉它、记一条错误日志,而不是让 panic 逃逸出去。
|
||||
e.store = nil
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Errorf("safeRunTask 没有拦住 panic: %v", r)
|
||||
}
|
||||
}()
|
||||
e.safeRunTask(context.Background(), Task{GID: "999", Token: "tok"})
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("safeRunTask 卡住了")
|
||||
}
|
||||
|
||||
if !sink.sawError() {
|
||||
t.Error("panic 应该被记录成错误日志,好让用户知道出了什么事")
|
||||
}
|
||||
}
|
||||
|
||||
// panicSink 记录错误日志
|
||||
type panicSink struct {
|
||||
mu sync.Mutex
|
||||
errs []string
|
||||
}
|
||||
|
||||
func (p *panicSink) TaskUpdate(Task) {}
|
||||
|
||||
func (p *panicSink) Log(level, msg string) {
|
||||
if level == "error" {
|
||||
p.mu.Lock()
|
||||
p.errs = append(p.errs, msg)
|
||||
p.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *panicSink) Done() {}
|
||||
|
||||
func (p *panicSink) sawError() bool {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return len(p.errs) > 0
|
||||
}
|
||||
|
||||
// 精准复现崩溃窗口:一路持续写快照里的 map(原 bug 的形态),
|
||||
// 另一路持续序列化快照。若快照与原 task 共享内存,这里会直接 fatal。
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user