mirror of
https://github.com/charlienet/go-mixed.git
synced 2025-07-18 00:22:41 +08:00
82 lines
1.7 KiB
Go
82 lines
1.7 KiB
Go
package panic
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"runtime/debug"
|
||
)
|
||
|
||
type Panic struct {
|
||
R any
|
||
Stack []byte
|
||
}
|
||
|
||
func (p Panic) String() string {
|
||
return fmt.Sprintf("%v\n%s", p.R, p.Stack)
|
||
}
|
||
|
||
type PanicGroup struct {
|
||
panics chan Panic // 致命错误通知
|
||
dones chan int // 协程完成通知
|
||
jobs chan int // 并发数量
|
||
jobN int32 // 工作协程数量
|
||
}
|
||
|
||
func NewPanicGroup(maxConcurrent int) *PanicGroup {
|
||
return &PanicGroup{
|
||
panics: make(chan Panic, 8),
|
||
dones: make(chan int, 8),
|
||
jobs: make(chan int, maxConcurrent),
|
||
}
|
||
}
|
||
|
||
func (g *PanicGroup) Go(f func()) *PanicGroup {
|
||
g.jobN++
|
||
|
||
go func() {
|
||
g.jobs <- 1
|
||
defer func() {
|
||
<-g.jobs
|
||
// go 语言只能在自己的协程中捕获自己的 panic
|
||
// 如果不处理,整个*进程*都会退出
|
||
if r := recover(); r != nil {
|
||
g.panics <- Panic{R: r, Stack: debug.Stack()}
|
||
// 如果发生 panic 就不再通知 Wait() 已完成
|
||
// 不然就可能出现 g.jobN 为 0 但 g.panics 非空
|
||
// 的情况,此时 Wait() 方法需要在正常结束的分支
|
||
// 中再额外检查是否发生了 panic,非常麻烦
|
||
return
|
||
}
|
||
|
||
g.dones <- 1
|
||
}()
|
||
|
||
f()
|
||
}()
|
||
|
||
return g
|
||
}
|
||
|
||
func (g *PanicGroup) Wait(ctx context.Context) error {
|
||
if g.jobN == 0 {
|
||
panic("no job to wait")
|
||
}
|
||
|
||
for {
|
||
select {
|
||
case <-g.dones: // 协程正常结束
|
||
g.jobN--
|
||
if g.jobN == 0 {
|
||
return nil
|
||
}
|
||
case p := <-g.panics: // 协程有 panic
|
||
panic(p)
|
||
case <-ctx.Done():
|
||
// 整个 ctx 结束,超时或者调用方主动取消
|
||
// 子协程应该共用该 ctx,都会收到相同的结束信号
|
||
// 不需要在这里再去通知各协程结束(实现起来也麻烦)
|
||
return ctx.Err()
|
||
}
|
||
}
|
||
}
|