From dac879b75eba8e43986ce4588f8f926d7f8f1f50 Mon Sep 17 00:00:00 2001 From: merlin Date: Tue, 14 Mar 2023 20:25:42 +0300 Subject: [PATCH 1/4] iter-err: add ForEachErr and ForEachIdxErr --- iter/iter.go | 71 +++++++++++ iter/iter_test.go | 308 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 379 insertions(+) diff --git a/iter/iter.go b/iter/iter.go index 124b4f9..6851eac 100644 --- a/iter/iter.go +++ b/iter/iter.go @@ -2,9 +2,11 @@ package iter import ( "runtime" + "sync" "sync/atomic" "github.com/sourcegraph/conc" + "github.com/sourcegraph/conc/internal/multierror" ) // defaultMaxGoroutines returns the default maximum number of @@ -22,6 +24,10 @@ type Iterator[T any] struct { // // If unset, MaxGoroutines defaults to runtime.GOMAXPROCS(0). MaxGoroutines int + + // FailFast controls whether the ForEach*Err methods should + // stop iterating over the input if an error is returned by callback. + FailFast bool } // ForEach executes f in parallel over each element in input. @@ -54,6 +60,19 @@ func (iter Iterator[T]) ForEach(input []T, f func(*T)) { // index of the element to the callback. func ForEachIdx[T any](input []T, f func(int, *T)) { Iterator[T]{}.ForEachIdx(input, f) } +// ForEachIdxErr is the same as ForEachIdx except it returns an error. +// This function will not stop iterating over the input if an error is returned by callback. +// All returned errors will be returned as a multierror. +func ForEachIdxErr[T any](input []T, f func(int, *T) error) error { + return Iterator[T]{}.ForEachIdxErr(input, f) +} + +// ForEachErr is the same as ForEach except it returns an error. +// This function will not stop iterating over the input if an error is returned by callback. +func ForEachErr[T any](input []T, f func(*T) error) error { + return Iterator[T]{}.ForEachErr(input, f) +} + // ForEachIdx is the same as ForEach except it also provides the // index of the element to the callback. func (iter Iterator[T]) ForEachIdx(input []T, f func(int, *T)) { @@ -83,3 +102,55 @@ func (iter Iterator[T]) ForEachIdx(input []T, f func(int, *T)) { } wg.Wait() } + +// ForEachErr is the same as ForEach except it returns an error. +// If Iterator.FailFast is true, ForEachErr will stop iterating on the first error. +// If Iterator.FailFast is false, ForEachErr will return all errors and iterate over the all the input. +func (iter Iterator[T]) ForEachErr(input []T, f func(*T) error) error { + return iter.ForEachIdxErr(input, func(_ int, t *T) error { + return f(t) + }) +} + +// ForEachIdxErr is the same as ForEachIdx except it returns an error. +// If Iterator.FailFast is true, ForEachIdxErr will stop iterating on the first error. +// If Iterator.FailFast is false, ForEachIdxErr will return all errors and iterate over the all the input. +func (iter Iterator[T]) ForEachIdxErr(input []T, f func(int, *T) error) error { + if iter.MaxGoroutines == 0 { + // iter is a value receiver and is hence safe to mutate + iter.MaxGoroutines = defaultMaxGoroutines() + } + + numInput := len(input) + if iter.MaxGoroutines > numInput { + // No more concurrent tasks than the number of input items. + iter.MaxGoroutines = numInput + } + + var errs error + var errsMu sync.Mutex + var idx atomic.Int64 + var failed atomic.Bool + + // Create the task outside the loop to avoid extra closure allocations. + task := func() { + i := int(idx.Add(1) - 1) + for ; i < numInput && !failed.Load(); i = int(idx.Add(1) - 1) { + if err := f(i, &input[i]); err != nil { + errsMu.Lock() + errs = multierror.Join(errs, err) + errsMu.Unlock() + + failed.Store(iter.FailFast) + } + } + } + + var wg conc.WaitGroup + for i := 0; i < iter.MaxGoroutines && !failed.Load(); i++ { + wg.Go(task) + } + wg.Wait() + + return errs +} diff --git a/iter/iter_test.go b/iter/iter_test.go index d65af2b..1d9853e 100644 --- a/iter/iter_test.go +++ b/iter/iter_test.go @@ -1,8 +1,10 @@ package iter import ( + "errors" "fmt" "strconv" + "sync" "sync/atomic" "testing" @@ -166,6 +168,312 @@ func TestForEach(t *testing.T) { }) } +func TestForIterator_EachIdxErr(t *testing.T) { + t.Parallel() + + t.Run("failFast=false", func(t *testing.T) { + it := Iterator[int]{MaxGoroutines: 999} + forEach := noIndex(it.ForEachIdxErr) + testForEachErr(t, false, forEach) + }) + + t.Run("failFast=true", func(t *testing.T) { + it := Iterator[int]{MaxGoroutines: 999} + forEach := noIndex(it.ForEachIdxErr) + testForEachErr(t, true, forEach) + }) + + t.Run("failfast", func(t *testing.T) { + t.Parallel() + + input := []int{1, 2, 3, 4, 5} + errTest := errors.New("test error") + iterator := Iterator[int]{MaxGoroutines: 1, FailFast: true} + + var mu sync.Mutex + var results []int + + err := iterator.ForEachIdxErr(input, func(_ int, t *int) error { + mu.Lock() + results = append(results, *t) + mu.Unlock() + + return errTest + }) + + require.ErrorIs(t, err, errTest) + require.Len(t, results, 1, "results") + require.Containsf(t, input, results[0], "results") + }) + + t.Run("safe for reuse", func(t *testing.T) { + t.Parallel() + + iterator := Iterator[int]{MaxGoroutines: 999} + + // iter.Concurrency > numInput case that updates iter.Concurrency + iterator.ForEachIdxErr([]int{1, 2, 3}, func(i int, t *int) error { + return nil + }) + + require.Equal(t, iterator.MaxGoroutines, 999) + }) + + t.Run("allows more than defaultMaxGoroutines() concurrent tasks", func(t *testing.T) { + t.Parallel() + + wantConcurrency := 2 * defaultMaxGoroutines() + + maxConcurrencyHit := make(chan struct{}) + + tasks := make([]int, wantConcurrency) + iterator := Iterator[int]{MaxGoroutines: wantConcurrency} + + var concurrentTasks atomic.Int64 + iterator.ForEachIdxErr(tasks, func(_ int, t *int) error { + n := concurrentTasks.Add(1) + defer concurrentTasks.Add(-1) + + if int(n) == wantConcurrency { + // All our tasks are running concurrently. + // Signal to the rest of the tasks to stop. + close(maxConcurrencyHit) + } else { + // Wait until we hit max concurrency before exiting. + // This ensures that all tasks have been started + // in parallel, despite being a larger input set than + // defaultMaxGoroutines(). + <-maxConcurrencyHit + } + + return nil + }) + }) +} + +func TestForIterator_EachErr(t *testing.T) { + t.Parallel() + + t.Run("failFast=false", func(t *testing.T) { + it := Iterator[int]{MaxGoroutines: 999} + testForEachErr(t, false, it.ForEachErr) + }) + + t.Run("failFast=true", func(t *testing.T) { + it := Iterator[int]{MaxGoroutines: 999} + testForEachErr(t, true, it.ForEachErr) + }) + + t.Run("safe for reuse", func(t *testing.T) { + t.Parallel() + + iterator := Iterator[int]{MaxGoroutines: 999} + + // iter.Concurrency > numInput case that updates iter.Concurrency + iterator.ForEachErr([]int{1, 2, 3}, func(t *int) error { + return nil + }) + + require.Equal(t, iterator.MaxGoroutines, 999) + }) + + t.Run("failfast", func(t *testing.T) { + t.Parallel() + + input := []int{1, 2, 3, 4, 5} + errTest := errors.New("test error") + iterator := Iterator[int]{MaxGoroutines: 1, FailFast: true} + + var mu sync.Mutex + var results []int + + err := iterator.ForEachErr(input, func(t *int) error { + mu.Lock() + results = append(results, *t) + mu.Unlock() + + return errTest + }) + + require.ErrorIs(t, err, errTest) + require.Len(t, results, 1, "results") + require.Containsf(t, input, results[0], "results") + }) + + t.Run("allows more than defaultMaxGoroutines() concurrent tasks", func(t *testing.T) { + t.Parallel() + + wantConcurrency := 2 * defaultMaxGoroutines() + + maxConcurrencyHit := make(chan struct{}) + + tasks := make([]int, wantConcurrency) + iterator := Iterator[int]{MaxGoroutines: wantConcurrency} + + var concurrentTasks atomic.Int64 + iterator.ForEachErr(tasks, func(t *int) error { + n := concurrentTasks.Add(1) + defer concurrentTasks.Add(-1) + + if int(n) == wantConcurrency { + // All our tasks are running concurrently. + // Signal to the rest of the tasks to stop. + close(maxConcurrencyHit) + } else { + // Wait until we hit max concurrency before exiting. + // This ensures that all tasks have been started + // in parallel, despite being a larger input set than + // defaultMaxGoroutines(). + <-maxConcurrencyHit + } + + return nil + }) + }) +} + +func TestForEachIdxErr(t *testing.T) { + t.Parallel() + + t.Run("standart", func(t *testing.T) { + forEach := noIndex(ForEachIdxErr[int]) + testForEachErr(t, false, forEach) + }) + + t.Run("unique indexes", func(t *testing.T) { + ints := []int{1, 2, 3, 4, 5} + got := []int{} + gotMu := sync.Mutex{} + + err := ForEachIdxErr(ints, func(i int, _ *int) error { + gotMu.Lock() + defer gotMu.Unlock() + got = append(got, i) + return nil + }) + + require.NoError(t, err) + require.ElementsMatch(t, got, []int{0, 1, 2, 3, 4}) + }) +} + +func TestForEachErr(t *testing.T) { + t.Parallel() + + testForEachErr(t, false, ForEachErr[int]) +} + +// noIndex converts a ForEachIdxErr function (or method) into a ForEachErr function (or method). +func noIndex(forEach func([]int, func(int, *int) error) error) func([]int, func(*int) error) error { + return func(ints []int, fn func(*int) error) error { + return forEach(ints, func(_ int, val *int) error { + return fn(val) + }) + } +} + +// testForEachErr runs a set of tests against a ForEachErr function (or method). +func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) error) error) { + t.Run("empty", func(t *testing.T) { + t.Parallel() + f := func() { + ints := []int{} + forEach(ints, func(val *int) error { + panic("this should never be called") + }) + } + require.NotPanics(t, f) + }) + + t.Run("panic is propagated", func(t *testing.T) { + t.Parallel() + f := func() { + ints := []int{1} + forEach(ints, func(val *int) error { + panic("super bad thing happened") + }) + } + require.Panics(t, f) + }) + + t.Run("mutating inputs is fine", func(t *testing.T) { + t.Parallel() + ints := []int{1, 2, 3, 4, 5} + forEach(ints, func(val *int) error { + *val += 1 + return nil + }) + require.Equal(t, []int{2, 3, 4, 5, 6}, ints) + }) + + t.Run("mutating inputs is fine", func(t *testing.T) { + t.Parallel() + ints := []int{1, 2, 3, 4, 5} + forEach(ints, func(val *int) error { + *val += 1 + return nil + }) + require.Equal(t, []int{2, 3, 4, 5, 6}, ints) + }) + + t.Run("returning errors", func(t *testing.T) { + t.Parallel() + ints := []int{1, 2, 3, 4, 5} + errs := []error{ + errors.New("error 1"), + errors.New("error 2"), + } + + err := forEach(ints, func(i *int) error { + return errs[*i%len(errs)] + }) + + switch { + case failFast: + assertAnyErrorIs(t, err, errs) + default: + assertAllErrorIs(t, err, errs) + } + }) + + t.Run("huge inputs", func(t *testing.T) { + t.Parallel() + ints := make([]int, 10000) + forEach(ints, func(val *int) error { + *val = 1 + + return nil + }) + expected := make([]int, 10000) + for i := 0; i < 10000; i++ { + expected[i] = 1 + } + require.Equal(t, expected, ints) + }) +} + +func assertAllErrorIs(t *testing.T, errs error, targets []error) { + t.Helper() + + for _, target := range targets { + if !errors.Is(errs, target) { + t.Errorf("expected error to be %v, got %v", target, errs) + } + } +} + +func assertAnyErrorIs(t *testing.T, errs error, targets []error) { + t.Helper() + + for _, target := range targets { + if errors.Is(errs, target) { + return + } + } + + t.Errorf("expected any error to be one of %v, got %v", targets, errs) +} + func BenchmarkForEach(b *testing.B) { for _, count := range []int{0, 1, 8, 100, 1000, 10000, 100000} { b.Run(strconv.Itoa(count), func(b *testing.B) { From f462796b58257e040db7e9e7942798742126cc9a Mon Sep 17 00:00:00 2001 From: merlin Date: Wed, 15 Mar 2023 14:15:28 +0300 Subject: [PATCH 2/4] iter-err: fix linter issues --- iter/iter_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/iter/iter_test.go b/iter/iter_test.go index 1d9853e..8bc7bb4 100644 --- a/iter/iter_test.go +++ b/iter/iter_test.go @@ -212,7 +212,7 @@ func TestForIterator_EachIdxErr(t *testing.T) { iterator := Iterator[int]{MaxGoroutines: 999} // iter.Concurrency > numInput case that updates iter.Concurrency - iterator.ForEachIdxErr([]int{1, 2, 3}, func(i int, t *int) error { + _ = iterator.ForEachIdxErr([]int{1, 2, 3}, func(i int, t *int) error { return nil }) @@ -230,7 +230,7 @@ func TestForIterator_EachIdxErr(t *testing.T) { iterator := Iterator[int]{MaxGoroutines: wantConcurrency} var concurrentTasks atomic.Int64 - iterator.ForEachIdxErr(tasks, func(_ int, t *int) error { + _ = iterator.ForEachIdxErr(tasks, func(_ int, t *int) error { n := concurrentTasks.Add(1) defer concurrentTasks.Add(-1) @@ -270,7 +270,7 @@ func TestForIterator_EachErr(t *testing.T) { iterator := Iterator[int]{MaxGoroutines: 999} // iter.Concurrency > numInput case that updates iter.Concurrency - iterator.ForEachErr([]int{1, 2, 3}, func(t *int) error { + _ = iterator.ForEachErr([]int{1, 2, 3}, func(t *int) error { return nil }) @@ -311,7 +311,7 @@ func TestForIterator_EachErr(t *testing.T) { iterator := Iterator[int]{MaxGoroutines: wantConcurrency} var concurrentTasks atomic.Int64 - iterator.ForEachErr(tasks, func(t *int) error { + _ = iterator.ForEachErr(tasks, func(t *int) error { n := concurrentTasks.Add(1) defer concurrentTasks.Add(-1) @@ -378,7 +378,7 @@ func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) t.Parallel() f := func() { ints := []int{} - forEach(ints, func(val *int) error { + _ = forEach(ints, func(val *int) error { panic("this should never be called") }) } @@ -389,7 +389,7 @@ func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) t.Parallel() f := func() { ints := []int{1} - forEach(ints, func(val *int) error { + _ = forEach(ints, func(val *int) error { panic("super bad thing happened") }) } @@ -399,7 +399,7 @@ func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) t.Run("mutating inputs is fine", func(t *testing.T) { t.Parallel() ints := []int{1, 2, 3, 4, 5} - forEach(ints, func(val *int) error { + _ = forEach(ints, func(val *int) error { *val += 1 return nil }) @@ -409,7 +409,7 @@ func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) t.Run("mutating inputs is fine", func(t *testing.T) { t.Parallel() ints := []int{1, 2, 3, 4, 5} - forEach(ints, func(val *int) error { + _ = forEach(ints, func(val *int) error { *val += 1 return nil }) @@ -439,7 +439,7 @@ func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) t.Run("huge inputs", func(t *testing.T) { t.Parallel() ints := make([]int, 10000) - forEach(ints, func(val *int) error { + _ = forEach(ints, func(val *int) error { *val = 1 return nil From 0bfcec1d8d92cf6484c5696ba7c765b18c75a14f Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 26 Apr 2024 18:26:55 +0300 Subject: [PATCH 3/4] refine ForEachIdxErr --- iter/iter.go | 14 ++++++++------ iter/iter_test.go | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/iter/iter.go b/iter/iter.go index 6851eac..5f3358e 100644 --- a/iter/iter.go +++ b/iter/iter.go @@ -1,12 +1,12 @@ package iter import ( + "errors" "runtime" "sync" "sync/atomic" "github.com/sourcegraph/conc" - "github.com/sourcegraph/conc/internal/multierror" ) // defaultMaxGoroutines returns the default maximum number of @@ -127,7 +127,7 @@ func (iter Iterator[T]) ForEachIdxErr(input []T, f func(int, *T) error) error { iter.MaxGoroutines = numInput } - var errs error + var errs []error var errsMu sync.Mutex var idx atomic.Int64 var failed atomic.Bool @@ -137,9 +137,11 @@ func (iter Iterator[T]) ForEachIdxErr(input []T, f func(int, *T) error) error { i := int(idx.Add(1) - 1) for ; i < numInput && !failed.Load(); i = int(idx.Add(1) - 1) { if err := f(i, &input[i]); err != nil { - errsMu.Lock() - errs = multierror.Join(errs, err) - errsMu.Unlock() + if alreadyFailedFast := failed.Swap(iter.FailFast); !alreadyFailedFast { + errsMu.Lock() + errs = append(errs, err) + errsMu.Unlock() + } failed.Store(iter.FailFast) } @@ -152,5 +154,5 @@ func (iter Iterator[T]) ForEachIdxErr(input []T, f func(int, *T) error) error { } wg.Wait() - return errs + return errors.Join(errs...) } diff --git a/iter/iter_test.go b/iter/iter_test.go index de360b9..539803b 100644 --- a/iter/iter_test.go +++ b/iter/iter_test.go @@ -174,13 +174,13 @@ func TestForIterator_EachIdxErr(t *testing.T) { t.Parallel() t.Run("failFast=false", func(t *testing.T) { - it := Iterator[int]{MaxGoroutines: 999} + it := iter.Iterator[int]{MaxGoroutines: 999} forEach := noIndex(it.ForEachIdxErr) testForEachErr(t, false, forEach) }) t.Run("failFast=true", func(t *testing.T) { - it := Iterator[int]{MaxGoroutines: 999} + it := iter.Iterator[int]{MaxGoroutines: 999} forEach := noIndex(it.ForEachIdxErr) testForEachErr(t, true, forEach) }) @@ -190,7 +190,7 @@ func TestForIterator_EachIdxErr(t *testing.T) { input := []int{1, 2, 3, 4, 5} errTest := errors.New("test error") - iterator := Iterator[int]{MaxGoroutines: 1, FailFast: true} + iterator := iter.Iterator[int]{MaxGoroutines: 1, FailFast: true} var mu sync.Mutex var results []int @@ -211,7 +211,7 @@ func TestForIterator_EachIdxErr(t *testing.T) { t.Run("safe for reuse", func(t *testing.T) { t.Parallel() - iterator := Iterator[int]{MaxGoroutines: 999} + iterator := iter.Iterator[int]{MaxGoroutines: 999} // iter.Concurrency > numInput case that updates iter.Concurrency _ = iterator.ForEachIdxErr([]int{1, 2, 3}, func(i int, t *int) error { @@ -224,12 +224,12 @@ func TestForIterator_EachIdxErr(t *testing.T) { t.Run("allows more than defaultMaxGoroutines() concurrent tasks", func(t *testing.T) { t.Parallel() - wantConcurrency := 2 * defaultMaxGoroutines() + wantConcurrency := 2 * iter.DefaultMaxGoroutines() maxConcurrencyHit := make(chan struct{}) tasks := make([]int, wantConcurrency) - iterator := Iterator[int]{MaxGoroutines: wantConcurrency} + iterator := iter.Iterator[int]{MaxGoroutines: wantConcurrency} var concurrentTasks atomic.Int64 _ = iterator.ForEachIdxErr(tasks, func(_ int, t *int) error { @@ -257,19 +257,19 @@ func TestForIterator_EachErr(t *testing.T) { t.Parallel() t.Run("failFast=false", func(t *testing.T) { - it := Iterator[int]{MaxGoroutines: 999} + it := iter.Iterator[int]{MaxGoroutines: 999} testForEachErr(t, false, it.ForEachErr) }) t.Run("failFast=true", func(t *testing.T) { - it := Iterator[int]{MaxGoroutines: 999} + it := iter.Iterator[int]{MaxGoroutines: 999} testForEachErr(t, true, it.ForEachErr) }) t.Run("safe for reuse", func(t *testing.T) { t.Parallel() - iterator := Iterator[int]{MaxGoroutines: 999} + iterator := iter.Iterator[int]{MaxGoroutines: 999} // iter.Concurrency > numInput case that updates iter.Concurrency _ = iterator.ForEachErr([]int{1, 2, 3}, func(t *int) error { @@ -284,7 +284,7 @@ func TestForIterator_EachErr(t *testing.T) { input := []int{1, 2, 3, 4, 5} errTest := errors.New("test error") - iterator := Iterator[int]{MaxGoroutines: 1, FailFast: true} + iterator := iter.Iterator[int]{MaxGoroutines: 1, FailFast: true} var mu sync.Mutex var results []int @@ -305,12 +305,12 @@ func TestForIterator_EachErr(t *testing.T) { t.Run("allows more than defaultMaxGoroutines() concurrent tasks", func(t *testing.T) { t.Parallel() - wantConcurrency := 2 * defaultMaxGoroutines() + wantConcurrency := 2 * iter.DefaultMaxGoroutines() maxConcurrencyHit := make(chan struct{}) tasks := make([]int, wantConcurrency) - iterator := Iterator[int]{MaxGoroutines: wantConcurrency} + iterator := iter.Iterator[int]{MaxGoroutines: wantConcurrency} var concurrentTasks atomic.Int64 _ = iterator.ForEachErr(tasks, func(t *int) error { @@ -338,7 +338,7 @@ func TestForEachIdxErr(t *testing.T) { t.Parallel() t.Run("standart", func(t *testing.T) { - forEach := noIndex(ForEachIdxErr[int]) + forEach := noIndex(iter.ForEachIdxErr[int]) testForEachErr(t, false, forEach) }) @@ -347,7 +347,7 @@ func TestForEachIdxErr(t *testing.T) { got := []int{} gotMu := sync.Mutex{} - err := ForEachIdxErr(ints, func(i int, _ *int) error { + err := iter.ForEachIdxErr(ints, func(i int, _ *int) error { gotMu.Lock() defer gotMu.Unlock() got = append(got, i) @@ -362,7 +362,7 @@ func TestForEachIdxErr(t *testing.T) { func TestForEachErr(t *testing.T) { t.Parallel() - testForEachErr(t, false, ForEachErr[int]) + testForEachErr(t, false, iter.ForEachErr[int]) } // noIndex converts a ForEachIdxErr function (or method) into a ForEachErr function (or method). From 3915b646f703b57fa9f5e9135f6e57007309e09f Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 26 Apr 2024 18:28:52 +0300 Subject: [PATCH 4/4] remove extra test --- iter/iter_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/iter/iter_test.go b/iter/iter_test.go index 539803b..428f9e6 100644 --- a/iter/iter_test.go +++ b/iter/iter_test.go @@ -408,16 +408,6 @@ func testForEachErr(t *testing.T, failFast bool, forEach func([]int, func(*int) require.Equal(t, []int{2, 3, 4, 5, 6}, ints) }) - t.Run("mutating inputs is fine", func(t *testing.T) { - t.Parallel() - ints := []int{1, 2, 3, 4, 5} - _ = forEach(ints, func(val *int) error { - *val += 1 - return nil - }) - require.Equal(t, []int{2, 3, 4, 5, 6}, ints) - }) - t.Run("returning errors", func(t *testing.T) { t.Parallel() ints := []int{1, 2, 3, 4, 5}