github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/dm/pkg/checker/worker_pool.go (about) 1 // Copyright 2022 PingCAP, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package checker 15 16 import ( 17 "context" 18 19 "golang.org/x/sync/errgroup" 20 ) 21 22 // WorkerPool is a easy-to-use worker pool that can start workers by Go then use 23 // PutJob to send jobs to worker. After worker finished a job, the result is 24 // sequentially called by resultHandler function which is the parameter of 25 // NewWorkerPool or NewWorkerPoolWithContext. After caller send all jobs, it can 26 // call Wait to make sure all jobs are finished. 27 // The type parameter J means job, R means result. Type J MUST only share 28 // concurrent-safe member like *sql.DB. 29 type WorkerPool[J, R any] struct { 30 ctx context.Context 31 32 // the closing order is inCh -> outCh -> done 33 inCh chan J 34 outCh chan R 35 done chan struct{} 36 errGroup *errgroup.Group 37 } 38 39 // NewWorkerPool creates a new worker pool. 40 // The type parameter J means job, R means result. Type J MUST only share 41 // concurrent-safe member like *sql.DB. 42 func NewWorkerPool[J, R any](resultHandler func(R)) *WorkerPool[J, R] { 43 return NewWorkerPoolWithContext[J, R](context.Background(), resultHandler) 44 } 45 46 // NewWorkerPoolWithContext creates a new worker pool with a context which may 47 // be canceled from caller. 48 // The type parameter J means job, R means result. Type J MUST only share 49 // concurrent-safe member like *sql.DB. 50 func NewWorkerPoolWithContext[J, R any]( 51 ctx context.Context, 52 resultHandler func(R), 53 ) *WorkerPool[J, R] { 54 group, groupCtx := errgroup.WithContext(ctx) 55 ret := &WorkerPool[J, R]{ 56 ctx: groupCtx, 57 errGroup: group, 58 inCh: make(chan J), 59 outCh: make(chan R), 60 done: make(chan struct{}), 61 } 62 go func() { 63 for r := range ret.outCh { 64 resultHandler(r) 65 } 66 close(ret.done) 67 }() 68 69 return ret 70 } 71 72 // Go is like a builtin go keyword. handler represents the logic of worker, if 73 // the worker has initializing logic, caller can use method of structure or 74 // closure to refer to the initialized part. 75 func (p *WorkerPool[J, R]) Go(handler func(ctx context.Context, job J) (R, error)) { 76 p.errGroup.Go(func() error { 77 for { 78 select { 79 case <-p.ctx.Done(): 80 return p.ctx.Err() 81 case job, ok := <-p.inCh: 82 if !ok { 83 return nil 84 } 85 result, err := handler(p.ctx, job) 86 if err != nil { 87 return err 88 } 89 p.outCh <- result 90 } 91 } 92 }) 93 } 94 95 // PutJob sends a job to worker pool. The return value means whether the workers 96 // are stopped so caller can stop early. 97 func (p *WorkerPool[J, R]) PutJob(job J) bool { 98 select { 99 case <-p.ctx.Done(): 100 return false 101 case p.inCh <- job: 102 return true 103 } 104 } 105 106 // Wait waits all workers to finish. It will return the first error occurred in 107 // workers, or nil if no error. 108 // Other methods should not be called concurrent with Wait or after Wait. 109 func (p *WorkerPool[J, R]) Wait() error { 110 close(p.inCh) 111 err := p.errGroup.Wait() 112 close(p.outCh) 113 <-p.done 114 return err 115 }