[check-licenses] Limit concurrency in the tool.

Invocations of this tool are occasionally failing due to "failed to
create new OS thread". This patch limits the number of goroutines
spawned for filesystem work. The limit can be adjusted by setting the
GOMAXPROCS environment variable or invoking runtime.GOMAXPROCS(...) as
appropriate..

Bug: 76187
Change-Id: I285b95e560cc8756db53983290835f5706647bb9
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/525952
Fuchsia-Auto-Submit: Randall Bosetti <rlb@google.com>
Reviewed-by: Sean Cuff <seancuff@google.com>
Commit-Queue: Auto-Submit <auto-submit@fuchsia-infra.iam.gserviceaccount.com>
diff --git a/tools/check-licenses/traverse.go b/tools/check-licenses/traverse.go
index 31449f7..1118886 100644
--- a/tools/check-licenses/traverse.go
+++ b/tools/check-licenses/traverse.go
@@ -11,6 +11,7 @@
 	"io/ioutil"
 	"log"
 	"os"
+	"runtime"
 	"runtime/trace"
 	"sort"
 	"strings"
@@ -30,6 +31,9 @@
 func Run(ctx context.Context, config *Config) error {
 	var eg errgroup.Group
 	var err error
+	// Blocked goroutines do not count against GOMAXPROCS. Limit
+	// ourselves to at most doubling the thread count.
+	var workSem = make(chan int, runtime.GOMAXPROCS(0))
 	metrics := NewMetrics()
 
 	file_tree, err := NewFileTree(ctx, config.BaseDir, nil, config, metrics)
@@ -48,12 +52,14 @@
 		tree := tree
 		for licenseFile := range tree.SingleLicenseFiles {
 			licenseFile := licenseFile
+			workSem <- 1
 			eg.Go(func() error {
 				if err := processLicenseFile(licenseFile, metrics, licenses, config, tree); err != nil {
 					// error safe to ignore because eg. io.EOF means symlink hasn't been handled yet
 					// TODO(jcecil): Correctly skip symlink.
 					log.Printf("warning: %s. Skipping file: %s.\n", err, licenseFile)
 				}
+				<-workSem
 				return nil
 			})
 		}
@@ -78,11 +84,13 @@
 	r = trace.StartRegion(ctx, "regular file walk")
 	for file := range file_tree.getFileIterator() {
 		file := file
+		workSem <- 1
 		eg.Go(func() error {
 			if err := processFile(file, metrics, licenses, unlicensedFiles, config); err != nil {
 				// TODO(jcecil): Correctly skip symlink and return errors.
 				log.Printf("warning: %s. Skipping file: %s.\n", err, file.Path)
 			}
+			<-workSem
 			return nil
 		})
 	}