Skip to content

Commit c57e545

Browse files
committed
add NewWithContext
Before this patch, forwarding context cancellation to the workerpool tasks was difficult and required to spawn a goroutine to call wp.Close when the parent context was done. This patch introduce NewWithContext allowing callers to provide a parent context from which is derived the tasks context, removing the need to manually forward context cancellation. Signed-off-by: Alexandre Perrin <[email protected]>
1 parent 113f474 commit c57e545

File tree

2 files changed

+64
-1
lines changed

2 files changed

+64
-1
lines changed

workerpool.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ type WorkerPool struct {
5050
// New creates a new pool of workers where at most n workers process submitted
5151
// tasks concurrently. New panics if n ≤ 0.
5252
func New(n int) *WorkerPool {
53+
return NewWithContext(context.Background(), n)
54+
}
55+
56+
// NewWithContext creates a new pool of workers where at most n workers process submitted
57+
// tasks concurrently. New panics if n ≤ 0. The context is used as the parent context to the context of the task func passed to Submit.
58+
func NewWithContext(ctx context.Context, n int) *WorkerPool {
5359
if n <= 0 {
5460
panic(fmt.Sprintf("workerpool.New: n must be > 0, got %d", n))
5561
}
5662
wp := &WorkerPool{
5763
workers: make(chan struct{}, n),
5864
tasks: make(chan *task),
5965
}
60-
ctx, cancel := context.WithCancel(context.Background())
66+
ctx, cancel := context.WithCancel(ctx)
6167
wp.cancel = cancel
6268
go wp.run(ctx)
6369
return wp

workerpool_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,60 @@ func TestWorkerPoolClose(t *testing.T) {
402402
}
403403
wg.Wait() // all routines should have returned
404404
}
405+
406+
func TestWorkerPoolNewWithContext(t *testing.T) {
407+
ctx, cancel := context.WithCancel(context.Background())
408+
n := runtime.NumCPU()
409+
wp := workerpool.NewWithContext(ctx, n)
410+
411+
// working is written to by each task as soon as possible.
412+
working := make(chan struct{})
413+
var wg sync.WaitGroup
414+
// Create n tasks waiting on the context to be cancelled.
415+
wg.Add(n)
416+
for i := 0; i < n; i++ {
417+
id := fmt.Sprintf("task #%2d", i)
418+
err := wp.Submit(id, func(ctx context.Context) error {
419+
working <- struct{}{}
420+
<-ctx.Done()
421+
wg.Done()
422+
return ctx.Err()
423+
})
424+
if err != nil {
425+
t.Errorf("failed to submit task '%s': %v", id, err)
426+
}
427+
}
428+
429+
// ensure n workers are busy
430+
for i := 0; i < n; i++ {
431+
<-working
432+
}
433+
434+
// cancel the parent context, which should complete all tasks.
435+
cancel()
436+
wg.Wait()
437+
438+
// Submitting a task once the parent context has been cancelled should
439+
// succeed and give a cancelled context to the task. This is not ideal and
440+
// might change in the future.
441+
wg.Add(1)
442+
id := "last"
443+
err := wp.Submit(id, func(ctx context.Context) error {
444+
defer wg.Done()
445+
select {
446+
case <-ctx.Done():
447+
default:
448+
t.Errorf("last task expected context to be cancelled")
449+
}
450+
return nil
451+
})
452+
if err != nil {
453+
t.Errorf("failed to submit task '%s': %v", id, err)
454+
}
455+
456+
wg.Wait()
457+
458+
if err := wp.Close(); err != nil {
459+
t.Errorf("close: got '%v', want no error", err)
460+
}
461+
}

0 commit comments

Comments
 (0)