gopls/internal/lsp/cache: fix data race in Symbols

We forgot to acquire a lock while accessing snapshot.files.

Also, upgrade errgroup and use its new SetLimit feature.

Change-Id: Iff1449fd727f0766f6c81391acccaa21951f59be
Reviewed-on: https://go-review.googlesource.com/c/tools/+/452058
Auto-Submit: Alan Donovan <adonovan@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index ae5fe28..c2cb946 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -1094,32 +1094,37 @@
 
 // Symbols extracts and returns the symbols for each file in all the snapshot's views.
 func (s *snapshot) Symbols(ctx context.Context) map[span.URI][]source.Symbol {
+	// Read the set of Go files out of the snapshot.
+	var goFiles []source.VersionedFileHandle
+	s.mu.Lock()
+	s.files.Range(func(uri span.URI, f source.VersionedFileHandle) {
+		if s.View().FileKind(f) == source.Go {
+			goFiles = append(goFiles, f)
+		}
+	})
+	s.mu.Unlock()
+
+	// Symbolize them in parallel.
 	var (
 		group    errgroup.Group
-		nprocs   = 2 * runtime.GOMAXPROCS(-1)  // symbolize is a mix of I/O and CPU
-		iolimit  = make(chan struct{}, nprocs) // I/O limiting counting semaphore
+		nprocs   = 2 * runtime.GOMAXPROCS(-1) // symbolize is a mix of I/O and CPU
 		resultMu sync.Mutex
 		result   = make(map[span.URI][]source.Symbol)
 	)
-	s.files.Range(func(uri span.URI, f source.VersionedFileHandle) {
-		if s.View().FileKind(f) != source.Go {
-			return // workspace symbols currently supports only Go files.
-		}
-
-		// TODO(adonovan): upgrade errgroup and use group.SetLimit(nprocs).
-		iolimit <- struct{}{} // acquire token
+	group.SetLimit(nprocs)
+	for _, f := range goFiles {
+		f := f
 		group.Go(func() error {
-			defer func() { <-iolimit }() // release token
 			symbols, err := s.symbolize(ctx, f)
 			if err != nil {
 				return err
 			}
 			resultMu.Lock()
-			result[uri] = symbols
+			result[f.URI()] = symbols
 			resultMu.Unlock()
 			return nil
 		})
-	})
+	}
 	// Keep going on errors, but log the first failure.
 	// Partial results are better than no symbol results.
 	if err := group.Wait(); err != nil {