diff --git a/bloom/bloom.go b/bloom/bloom.go index 21342c7..267995e 100644 --- a/bloom/bloom.go +++ b/bloom/bloom.go @@ -26,7 +26,8 @@ func NewBloomFilter() *BloomFilter { } func (bf *BloomFilter) Add(value string) { - for _, f := range bf.funcs { + funcs := bf.funcs[:] + for _, f := range funcs { bf.set.Set(f.hash(value)) } } @@ -36,7 +37,9 @@ func (bf *BloomFilter) Contains(value string) bool { return false } ret := true - for _, f := range bf.funcs { + + funcs := bf.funcs[:] + for _, f := range funcs { ret = ret && bf.set.Test(f.hash(value)) } return ret diff --git a/bytesconv/byteResult.go b/bytesconv/byteResult.go index fc72915..db7e709 100644 --- a/bytesconv/byteResult.go +++ b/bytesconv/byteResult.go @@ -16,7 +16,9 @@ func (r BytesResult) Hex() string { func (r BytesResult) UppercaseHex() string { dst := make([]byte, hex.EncodedLen(len(r))) j := 0 - for _, v := range r { + + re := r[:] + for _, v := range re { dst[j] = hextable[v>>4] dst[j+1] = hextable[v&0x0f] j += 2 diff --git a/compress/zip.go b/compress/zip.go index 4aaa315..51196eb 100644 --- a/compress/zip.go +++ b/compress/zip.go @@ -42,7 +42,8 @@ func (z *zipPackage) Write(out *os.File) error { zipWriter := zip.NewWriter(out) defer zipWriter.Close() - for _, f := range z.files { + files := z.files + for _, f := range files { fileWriter, err := zipWriter.Create(f.name) if err != nil { return err diff --git a/maps/sort_map.go b/maps/sort_map.go index 8846c38..e0cc8d4 100644 --- a/maps/sort_map.go +++ b/maps/sort_map.go @@ -88,7 +88,8 @@ func (m *sorted_map[K, V]) Iter() <-chan *Entry[K, V] { } func (m *sorted_map[K, V]) ForEach(f func(K, V) bool) { - for _, k := range m.keys { + keys := m.keys[:] + for _, k := range keys { if v, ok := m.Get(k); ok { if f(k, v) { break diff --git a/panic/panic.go b/panic/panic.go new file mode 100644 index 0000000..19f526e --- /dev/null +++ b/panic/panic.go @@ -0,0 +1,81 @@ +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() + } + } +} diff --git a/panic/panic_test.go b/panic/panic_test.go new file mode 100644 index 0000000..cc15221 --- /dev/null +++ b/panic/panic_test.go @@ -0,0 +1,32 @@ +package panic + +import ( + "context" + "fmt" + "testing" + "time" +) + +func TestPanic(t *testing.T) { + defer func() { + t.Log("捕捉异常") + if e := recover(); e != nil { + if err, ok := e.(error); ok { + t.Log(err.Error()) + } + t.Log("格式化:", e) + } + }() + + g := NewPanicGroup(10) + g.Go(func() { + panic("1243") + }) + + if err := g.Wait(context.Background()); err != nil { + panic(err) + } + + time.Sleep(1 * time.Second) + fmt.Println("这条消息可打印") +} diff --git a/sort/sort.go b/sort/sort.go index 58a9046..19d4a1e 100644 --- a/sort/sort.go +++ b/sort/sort.go @@ -40,7 +40,9 @@ func (s *mapSorter[T]) Desc() *mapSorter[T] { func (s *mapSorter[T]) Join(sep string, f func(k string, v T) string) string { slice := make([]string, 0, len(s.m)) - for _, k := range s.keys { + + keys := s.keys[:] + for _, k := range keys { slice = append(slice, f(k, s.m[k])) } @@ -53,7 +55,9 @@ func (s *mapSorter[T]) Keys() []string { func (s *mapSorter[T]) Values() []T { ret := make([]T, 0, len(s.m)) - for _, k := range s.keys { + + keys := s.keys[:] + for _, k := range keys { ret = append(ret, s.m[k]) } diff --git a/structs/structs.go b/structs/structs.go index dc8632f..cbf66f9 100644 --- a/structs/structs.go +++ b/structs/structs.go @@ -36,7 +36,8 @@ func (s *Struct) Kind() reflect.Kind { func (s *Struct) Names() []string { names := make([]string, len(s.fields)) - for i, f := range s.fields { + fields := s.fields[:] + for i, f := range fields { names[i] = f.name } @@ -45,7 +46,9 @@ func (s *Struct) Names() []string { func (s *Struct) Values() []any { values := make([]any, 0, len(s.fields)) - for _, fi := range s.fields { + + fields := s.fields[:] + for _, fi := range fields { v := s.value.FieldByName(fi.name) values = append(values, v.Interface()) } @@ -54,7 +57,8 @@ func (s *Struct) Values() []any { } func (s *Struct) IsZero() bool { - for _, fi := range s.fields { + fields := s.fields[:] + for _, fi := range fields { source := s.value.FieldByName(fi.name) if !source.IsZero() { return false @@ -66,7 +70,9 @@ func (s *Struct) IsZero() bool { func (s *Struct) ToMap() map[string]any { m := make(map[string]any, len(s.fields)) - for _, fi := range s.fields { + + fields := s.fields[:] + for _, fi := range fields { source := s.value.FieldByName(fi.name) if fi.shouldIgnore(source) { continue @@ -109,7 +115,8 @@ func (s *Struct) Copy(dest any) error { } func (s *Struct) getByName(name string) (field, bool) { - for i := range s.fields { + fields := s.fields[:] + for i := range fields { f := s.fields[i] if f.name == name { return f, true