[fuzz] Fix concurrency issue when fuzzer fails in -check

The existing concurrent code in -check does not correctly handle when
a gorountine returns failure. Specifically it doesn't wait for the
other goroutines to finish their current task and stop before tearing
down the progress bar, thus there is a race that sometimes leads to
instead of outputting the error text of the failing run will cause a Go
panic due to trying to send on a channel that is closed.

In this CL I am removing the non-idiomatic concurrency code and
replacing it with usage of the standard libraries and extensions for
handling this case.

Since this was the only usage of RunConcurrent, and it is error prone,
I am also removing it.

Fixes: 561685245
Change-Id: Ib2ec8a2ce09180d42b1cccabf866ed803d37b56b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/342195
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/tools/src/cmd/fuzz/main.go b/tools/src/cmd/fuzz/main.go
index 308e579..afa163d 100644
--- a/tools/src/cmd/fuzz/main.go
+++ b/tools/src/cmd/fuzz/main.go
@@ -29,6 +29,8 @@
 package main
 
 import (
+	"context"
+	"errors"
 	"flag"
 	"fmt"
 	"os"
@@ -44,6 +46,8 @@
 	"dawn.googlesource.com/dawn/tools/src/progressbar"
 	"dawn.googlesource.com/dawn/tools/src/transform"
 	"dawn.googlesource.com/dawn/tools/src/utils"
+
+	"golang.org/x/sync/errgroup"
 )
 
 // TODO(crbug.com/416755658): Add unittest coverage when exec calls are done
@@ -531,25 +535,35 @@
 	defer pb.Stop()
 	var numDone uint32
 
-	routine := func() error {
-		for file := range remaining {
-			atomic.AddUint32(&numDone, 1)
-			pb.Update(progressbar.Status{
-				Total: len(files),
-				Segments: []progressbar.Segment{
-					{Count: int(atomic.LoadUint32(&numDone))},
-				},
-			})
+	eg, ctx := errgroup.WithContext(utils.CancelOnInterruptContext(context.Background()))
+	for i := 0; i < t.numProcesses; i++ {
+		eg.Go(func() error {
+			for {
+				select {
+				case <-ctx.Done():
+					return ctx.Err()
+				case file, ok := <-remaining:
+					if !ok {
+						return nil
+					}
+					atomic.AddUint32(&numDone, 1)
+					pb.Update(progressbar.Status{
+						Total: len(files),
+						Segments: []progressbar.Segment{
+							{Count: int(atomic.LoadUint32(&numDone))},
+						},
+					})
 
-			if out, err := t.runCmd(t.fuzzer, file); err != nil {
-				_, fuzzer := filepath.Split(t.fuzzer)
-				return fmt.Errorf("fuzzer '%s' failed to process file '%s' with error: %w\nOutput:\n%s", fuzzer, file, err, string(out))
+					if out, err := t.runCmd(t.fuzzer, file); err != nil {
+						_, fuzzer := filepath.Split(t.fuzzer)
+						return fmt.Errorf("fuzzer '%s' failed to process file '%s' with error: %w\nOutput:\n%s", fuzzer, file, err, string(out))
+					}
+				}
 			}
-		}
-		return nil
+		})
 	}
 
-	if err := utils.RunConcurrent(t.numProcesses, routine); err != nil {
+	if err := eg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
 		return err
 	}
 
diff --git a/tools/src/utils/run_concurrent.go b/tools/src/utils/run_concurrent.go
deleted file mode 100644
index dea78e8..0000000
--- a/tools/src/utils/run_concurrent.go
+++ /dev/null
@@ -1,55 +0,0 @@
-// Copyright 2022 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-//  1. Redistributions of source code must retain the above copyright notice, this
-//     list of conditions and the following disclaimer.
-//
-//  2. Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//
-//  3. Neither the name of the copyright holder nor the names of its
-//     contributors may be used to endorse or promote products derived from
-//     this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package utils
-
-import "sync"
-
-// RunConcurrent calls fn on numRoutines separate go routines.
-// RunConcurrent will return on the first error returned, leaving the other go
-// routines to complete.
-func RunConcurrent(numRoutines int, fn func() error) error {
-	errs := make(chan error, numRoutines)
-	wg := sync.WaitGroup{}
-	wg.Add(numRoutines)
-	for i := 0; i < numRoutines; i++ {
-		go func() {
-			defer wg.Done()
-			errs <- fn()
-		}()
-	}
-	go func() {
-		wg.Wait()
-		close(errs)
-	}()
-	for err := range errs {
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
diff --git a/tools/src/utils/run_concurrent_test.go b/tools/src/utils/run_concurrent_test.go
deleted file mode 100644
index 93f3d50..0000000
--- a/tools/src/utils/run_concurrent_test.go
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright 2022 The Dawn & Tint Authors
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are met:
-//
-//  1. Redistributions of source code must retain the above copyright notice, this
-//     list of conditions and the following disclaimer.
-//
-//  2. Redistributions in binary form must reproduce the above copyright notice,
-//     this list of conditions and the following disclaimer in the documentation
-//     and/or other materials provided with the distribution.
-//
-//  3. Neither the name of the copyright holder nor the names of its
-//     contributors may be used to endorse or promote products derived from
-//     this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
-// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
-// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
-// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package utils_test
-
-import (
-	"fmt"
-	"sync/atomic"
-	"testing"
-
-	"dawn.googlesource.com/dawn/tools/src/utils"
-)
-
-func TestRunConcurrent(t *testing.T) {
-	var i uint32
-	err := utils.RunConcurrent(8, func() error {
-		atomic.AddUint32(&i, 1)
-		return nil
-	})
-	if err != nil {
-		t.Errorf("utils.RunConcurrent() returned %v", err)
-	}
-	if n := atomic.LoadUint32(&i); n != 8 {
-		t.Errorf("i is %v", n)
-	}
-}
-
-func TestRunConcurrentError(t *testing.T) {
-	err := utils.RunConcurrent(8, func() error {
-		return fmt.Errorf("error")
-	})
-	if err == nil {
-		t.Errorf("utils.RunConcurrent() returned no error")
-	}
-}