Skip to content

Commit 584f556

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/cache: downgrade bug reports for inconsistent metadata
As described in the lengthy comment included with this CL, it is possible for gopls to encounter inconsistent metadata when it does not observe all filesystem changes. This explains the frequent bug reports of this nature that we have been seeing in telemetry. However, this is not feasible to address without significant redesign of gopls' package loading, and likely changes to the go command and go/packages integration. For now, we must downgrade the report. Fixes golang/go#63822 Change-Id: I60d627eab00a33a1f68fdf9f2de9890bede33e73 Reviewed-on: https://go-review.googlesource.com/c/tools/+/647515 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Robert Findley <[email protected]>
1 parent fa7774c commit 584f556

File tree

1 file changed

+90
-7
lines changed

1 file changed

+90
-7
lines changed

gopls/internal/cache/check.go

+90-7
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,13 @@ func (b *typeCheckBatch) importPackage(ctx context.Context, mp *metadata.Package
548548
}
549549
}
550550
} else {
551-
id = importLookup(PackagePath(item.Path))
551+
var alt PackageID
552+
id, alt = importLookup(PackagePath(item.Path))
553+
if alt != "" {
554+
// Any bug leading to this scenario would have already been reported
555+
// in importLookup.
556+
return fmt.Errorf("inconsistent metadata during import: for package path %q, found both IDs %q and %q", item.Path, id, alt)
557+
}
552558
var err error
553559
pkg, err = b.getImportPackage(ctx, id)
554560
if err != nil {
@@ -665,8 +671,12 @@ func (b *typeCheckBatch) checkPackageForImport(ctx context.Context, ph *packageH
665671
// a given package path, based on the forward transitive closure of the initial
666672
// package (id).
667673
//
674+
// If the second result is non-empty, it is another ID discovered in the import
675+
// graph for the same package path. This means the import graph is
676+
// incoherent--see #63822 and the long comment below.
677+
//
668678
// The resulting function is not concurrency safe.
669-
func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath) PackageID {
679+
func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath) (id, altID PackageID) {
670680
assert(mp != nil, "nil metadata")
671681

672682
// This function implements an incremental depth first scan through the
@@ -680,6 +690,10 @@ func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath
680690
mp.PkgPath: mp.ID,
681691
}
682692

693+
// altIDs records alternative IDs for the given path, to report inconsistent
694+
// metadata.
695+
var altIDs map[PackagePath]PackageID
696+
683697
// pending is a FIFO queue of package metadata that has yet to have its
684698
// dependencies fully scanned.
685699
// Invariant: all entries in pending are already mapped in impMap.
@@ -695,13 +709,82 @@ func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath
695709
if prevID, ok := impMap[depPath]; ok {
696710
// debugging #63822
697711
if prevID != depID {
712+
if altIDs == nil {
713+
altIDs = make(map[PackagePath]PackageID)
714+
}
715+
if _, ok := altIDs[depPath]; !ok {
716+
altIDs[depPath] = depID
717+
}
698718
prev := source.Metadata(prevID)
699719
curr := source.Metadata(depID)
700720
switch {
701721
case prev == nil || curr == nil:
702722
bug.Reportf("inconsistent view of dependencies (missing dep)")
703723
case prev.ForTest != curr.ForTest:
704-
bug.Reportf("inconsistent view of dependencies (mismatching ForTest)")
724+
// This case is unfortunately understood to be possible.
725+
//
726+
// To explain this, consider a package a_test testing the package
727+
// a, and for brevity denote by b' the intermediate test variant of
728+
// the package b, which is created for the import graph of a_test,
729+
// if b imports a.
730+
//
731+
// Now imagine that we have the following import graph, where
732+
// higher packages import lower ones.
733+
//
734+
// a_test
735+
// / \
736+
// b' c
737+
// / \ /
738+
// a d
739+
//
740+
// In this graph, there is one intermediate test variant b',
741+
// because b imports a and so b' must hold the test variant import.
742+
//
743+
// Now, imagine that an on-disk change (perhaps due to a branch
744+
// switch) affects the above import graph such that d imports a.
745+
//
746+
// a_test
747+
// / \
748+
// b' c*
749+
// / \ /
750+
// / d*
751+
// a---/
752+
//
753+
// In this case, c and d should really be intermediate test
754+
// variants, because they reach a. However, suppose that gopls does
755+
// not know this yet (as indicated by '*').
756+
//
757+
// Now suppose that the metadata of package c is invalidated, for
758+
// example due to a change in an unrelated import or an added file.
759+
// This will invalidate the metadata of c and a_test (but NOT b),
760+
// and now gopls observes this graph:
761+
//
762+
// a_test
763+
// / \
764+
// b' c'
765+
// /| |
766+
// / d d'
767+
// a-----/
768+
//
769+
// That is: a_test now sees c', which sees d', but since b was not
770+
// invalidated, gopls still thinks that b' imports d (not d')!
771+
//
772+
// The problem, of course, is that gopls never observed the change
773+
// to d, which would have invalidated b. This may be due to racing
774+
// file watching events, in which case the problem should
775+
// self-correct when gopls sees the change to d, or it may be due
776+
// to d being outside the coverage of gopls' file watching glob
777+
// patterns, or it may be due to buggy or entirely absent
778+
// client-side file watching.
779+
//
780+
// TODO(rfindley): fix this, one way or another. It would be hard
781+
// or impossible to repair gopls' state here, during type checking.
782+
// However, we could perhaps reload metadata in Snapshot.load until
783+
// we achieve a consistent state, or better, until the loaded state
784+
// is consistent with our view of the filesystem, by making the Go
785+
// command report digests of the files it reads. Both of those are
786+
// tricker than they may seem, and have significant performance
787+
// implications.
705788
default:
706789
bug.Reportf("inconsistent view of dependencies")
707790
}
@@ -723,16 +806,16 @@ func importLookup(mp *metadata.Package, source metadata.Source) func(PackagePath
723806
return id, found
724807
}
725808

726-
return func(pkgPath PackagePath) PackageID {
809+
return func(pkgPath PackagePath) (id, altID PackageID) {
727810
if id, ok := impMap[pkgPath]; ok {
728-
return id
811+
return id, altIDs[pkgPath]
729812
}
730813
for len(pending) > 0 {
731814
if id, found := search(pkgPath); found {
732-
return id
815+
return id, altIDs[pkgPath]
733816
}
734817
}
735-
return ""
818+
return "", ""
736819
}
737820
}
738821

0 commit comments

Comments
 (0)