mg.g.Require(MainModules.mustGetSingleMainModule(), roots)
}
+ type dedupKey struct {
+ m module.Version
+ pruning modPruning
+ }
var (
- loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
- loadingUnpruned sync.Map // module.Version → nil; the set of modules that have been or are being loaded via roots that do not support pruning
+ loadQueue = par.NewQueue(runtime.GOMAXPROCS(0))
+ loading sync.Map // dedupKey → nil; the set of modules that have been or are being loaded
)
// loadOne synchronously loads the explicit requirements for module m.
return
}
- if pruning == unpruned {
- if _, dup := loadingUnpruned.LoadOrStore(m, nil); dup {
- // m has already been enqueued for loading. Since unpruned loading may
- // follow cycles in the requirement graph, we need to return early
- // to avoid making the load queue infinitely long.
- return
- }
+ if _, dup := loading.LoadOrStore(dedupKey{m, pruning}, nil); dup {
+ // m has already been enqueued for loading. Since unpruned loading may
+ // follow cycles in the requirement graph, we need to return early
+ // to avoid making the load queue infinitely long.
+ return
}
loadQueue.Add(func() {
--- /dev/null
+# Regression test for https://go.dev/issue/60490: 'go get' should not cause an
+# infinite loop for cycles introduced in the pruned module graph.
+
+go get example.net/c@v0.1.0
+
+-- go.mod --
+module example
+
+go 1.19
+
+require (
+ example.net/a v0.1.0
+ example.net/b v0.1.0
+)
+
+replace (
+ example.net/a v0.1.0 => ./a1
+ example.net/a v0.2.0 => ./a2
+ example.net/b v0.1.0 => ./b1
+ example.net/b v0.2.0 => ./b2
+ example.net/c v0.1.0 => ./c1
+)
+-- a1/go.mod --
+module example.net/a
+
+go 1.19
+-- a2/go.mod --
+module example.net/a
+
+go 1.19
+
+require example.net/b v0.2.0
+-- b1/go.mod --
+module example.net/b
+
+go 1.19
+-- b2/go.mod --
+module example.net/b
+
+go 1.19
+
+require example.net/a v0.2.0
+-- c1/go.mod --
+module example.net/c
+
+go 1.19
+
+require example.net/a v0.2.0