internal/lsp: avoid extra work in *cache.View.remove

Fixes golang/go#31177

Change-Id: I31219c6285fecd0abc4ff5ec4ae732bcfcb69511
Reviewed-on: https://go-review.googlesource.com/c/tools/+/170182
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 67bed47..e6109a2 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -172,7 +172,7 @@
 
 	// Remove the package and all of its reverse dependencies from the cache.
 	if f.pkg != nil {
-		v.remove(f.pkg.pkgPath)
+		v.remove(f.pkg.pkgPath, map[string]struct{}{})
 	}
 
 	switch {
@@ -191,13 +191,17 @@
 // remove invalidates a package and its reverse dependencies in the view's
 // package cache. It is assumed that the caller has locked both the mutexes
 // of both the mcache and the pcache.
-func (v *View) remove(pkgPath string) {
+func (v *View) remove(pkgPath string, seen map[string]struct{}) {
+	if _, ok := seen[pkgPath]; ok {
+		return
+	}
 	m, ok := v.mcache.packages[pkgPath]
 	if !ok {
 		return
 	}
+	seen[pkgPath] = struct{}{}
 	for parentPkgPath := range m.parents {
-		v.remove(parentPkgPath)
+		v.remove(parentPkgPath, seen)
 	}
 	// All of the files in the package may also be holding a pointer to the
 	// invalidated package.