github.com/igoogolx/clash@v1.19.8/common/batch/batch.go (about) 1 package batch 2 3 import ( 4 "context" 5 "sync" 6 ) 7 8 type Option = func(b *Batch) 9 10 type Result struct { 11 Value any 12 Err error 13 } 14 15 type Error struct { 16 Key string 17 Err error 18 } 19 20 func WithConcurrencyNum(n int) Option { 21 return func(b *Batch) { 22 q := make(chan struct{}, n) 23 for i := 0; i < n; i++ { 24 q <- struct{}{} 25 } 26 b.queue = q 27 } 28 } 29 30 // Batch similar to errgroup, but can control the maximum number of concurrent 31 type Batch struct { 32 result map[string]Result 33 queue chan struct{} 34 wg sync.WaitGroup 35 mux sync.Mutex 36 err *Error 37 once sync.Once 38 cancel func() 39 } 40 41 func (b *Batch) Go(key string, fn func() (any, error)) { 42 b.wg.Add(1) 43 go func() { 44 defer b.wg.Done() 45 if b.queue != nil { 46 <-b.queue 47 defer func() { 48 b.queue <- struct{}{} 49 }() 50 } 51 52 value, err := fn() 53 if err != nil { 54 b.once.Do(func() { 55 b.err = &Error{key, err} 56 if b.cancel != nil { 57 b.cancel() 58 } 59 }) 60 } 61 62 ret := Result{value, err} 63 b.mux.Lock() 64 defer b.mux.Unlock() 65 b.result[key] = ret 66 }() 67 } 68 69 func (b *Batch) Wait() *Error { 70 b.wg.Wait() 71 if b.cancel != nil { 72 b.cancel() 73 } 74 return b.err 75 } 76 77 func (b *Batch) WaitAndGetResult() (map[string]Result, *Error) { 78 err := b.Wait() 79 return b.Result(), err 80 } 81 82 func (b *Batch) Result() map[string]Result { 83 b.mux.Lock() 84 defer b.mux.Unlock() 85 copy := map[string]Result{} 86 for k, v := range b.result { 87 copy[k] = v 88 } 89 return copy 90 } 91 92 func New(ctx context.Context, opts ...Option) (*Batch, context.Context) { 93 ctx, cancel := context.WithCancel(ctx) 94 95 b := &Batch{ 96 result: map[string]Result{}, 97 } 98 99 for _, o := range opts { 100 o(b) 101 } 102 103 b.cancel = cancel 104 return b, ctx 105 }