cmd/go/internal/modfetch: push codehost.Repo locking down one level

The modfetch.Repo needs to be safe for use by multiple goroutines,
but the codehost.Repo (a source code repository) was not,
so we added locking around the codehost.Repo methods.
But we might reasonably have multiple modfetch.Repos (modules)
drawing from a single codehost.Repo, especially if we cache
codehost.Repo construction. So push the locking further down.

After this CL, it would still be nice to add locking in package
codehost on a per-directory basis, so that codehost.Run calls
on a given directory are, or at least can be, serialized,
even when they are being invoked from different processes.
To do that we need to work out the right file locking on each
system, which we'll put off for now. (TODO added.)

(Git is already doing file locking to make access from multiple
processes safe at the level of individual commands.)

Change-Id: I9ce4ecf8f8b49cabea50b8663e3cee60ff95e7c9
Reviewed-on: https://go-review.googlesource.com/119775
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modfetch/codehost/codehost.go b/vendor/cmd/go/internal/modfetch/codehost/codehost.go
index 218bb1a..177be3a 100644
--- a/vendor/cmd/go/internal/modfetch/codehost/codehost.go
+++ b/vendor/cmd/go/internal/modfetch/codehost/codehost.go
@@ -32,6 +32,7 @@
 // A Repo represents a code hosting source.
 // Typical implementations include local version control repositories,
 // remote version control servers, and code hosting sites.
+// A Repo must be safe for simultaneous use by multiple goroutines.
 type Repo interface {
 	// Root returns the import path of the root directory of the repository.
 	Root() string
diff --git a/vendor/cmd/go/internal/modfetch/coderepo.go b/vendor/cmd/go/internal/modfetch/coderepo.go
index 703b350..ab2a052 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo.go
@@ -15,7 +15,6 @@
 	"path/filepath"
 	"regexp"
 	"strings"
-	"sync"
 	"time"
 
 	"cmd/go/internal/modconv"
@@ -32,7 +31,6 @@
 	code     codehost.Repo
 	codeRoot string
 	codeDir  string
-	codeMu   sync.Mutex // protects code methods
 
 	path        string
 	pathPrefix  string
@@ -98,9 +96,7 @@
 	if r.codeDir != "" {
 		p = r.codeDir + "/" + p
 	}
-	r.codeMu.Lock()
 	tags, err := r.code.Tags(p)
-	r.codeMu.Unlock()
 	if err != nil {
 		return nil, err
 	}
@@ -130,9 +126,7 @@
 	if semver.IsValid(codeRev) && r.codeDir != "" {
 		codeRev = r.codeDir + "/" + codeRev
 	}
-	r.codeMu.Lock()
 	info, err := r.code.Stat(codeRev)
-	r.codeMu.Unlock()
 	if err != nil {
 		return nil, err
 	}
@@ -140,9 +134,7 @@
 }
 
 func (r *codeRepo) Latest() (*RevInfo, error) {
-	r.codeMu.Lock()
 	info, err := r.code.Latest()
-	r.codeMu.Unlock()
 	if err != nil {
 		return nil, err
 	}
@@ -208,9 +200,7 @@
 			return rev, "", nil, nil
 		}
 		file1 := path.Join(r.codeDir, "go.mod")
-		r.codeMu.Lock()
 		gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
-		r.codeMu.Unlock()
 		if err1 != nil {
 			if os.IsNotExist(err1) {
 				return "", "", nil, errors.New("missing go.mod")
@@ -229,10 +219,8 @@
 	// a replace directive.
 	file1 := path.Join(r.codeDir, "go.mod")
 	file2 := path.Join(r.codeDir, r.pathMajor[1:], "go.mod")
-	r.codeMu.Lock()
 	gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
 	gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
-	r.codeMu.Unlock()
 
 	if err1 != nil && !os.IsNotExist(err1) {
 		return "", "", nil, fmt.Errorf("reading %s: %v", file1, err1)
@@ -271,9 +259,7 @@
 	if gomod != nil {
 		return gomod, nil
 	}
-	r.codeMu.Lock()
 	data, err = r.code.ReadFile(rev, path.Join(dir, "go.mod"), codehost.MaxGoMod)
-	r.codeMu.Unlock()
 	if err != nil {
 		if os.IsNotExist(err) {
 			return r.legacyGoMod(rev, dir), nil
@@ -300,9 +286,7 @@
 	mf := new(modfile.File)
 	mf.AddModuleStmt(r.modPath)
 	for _, file := range altConfigs {
-		r.codeMu.Lock()
 		data, err := r.code.ReadFile(rev, path.Join(dir, file), codehost.MaxGoMod)
-		r.codeMu.Unlock()
 		if err != nil {
 			continue
 		}
@@ -331,9 +315,7 @@
 	if err != nil {
 		return "", err
 	}
-	r.codeMu.Lock()
 	dl, actualDir, err := r.code.ReadZip(rev, dir, codehost.MaxZipFile)
-	r.codeMu.Unlock()
 	if err != nil {
 		return "", err
 	}
@@ -471,9 +453,7 @@
 	}
 
 	if !haveLICENSE && subdir != "" {
-		r.codeMu.Lock()
 		data, err := r.code.ReadFile(rev, "LICENSE", codehost.MaxLICENSE)
-		r.codeMu.Unlock()
 		if err == nil {
 			w, err := zw.Create(r.modPrefix(version) + "/LICENSE")
 			if err != nil {
diff --git a/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go b/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go
index 8a653a8..e078189 100644
--- a/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go
+++ b/vendor/cmd/go/internal/modfetch/gitrepo/fetch.go
@@ -8,7 +8,6 @@
 import (
 	"archive/zip"
 	"bytes"
-	"cmd/go/internal/modfetch/codehost"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -19,6 +18,9 @@
 	"strings"
 	"sync"
 	"time"
+
+	"cmd/go/internal/modfetch/codehost"
+	"cmd/go/internal/par"
 )
 
 // Repo returns the code repository at the given Git remote reference.
@@ -36,6 +38,27 @@
 
 const workDirType = "git2"
 
+var repoCache par.Cache
+
+func newRepoCached(remote, root string, localOK bool) (codehost.Repo, error) {
+	type key struct {
+		remote  string
+		root    string
+		localOK bool
+	}
+	type cached struct {
+		repo codehost.Repo
+		err  error
+	}
+
+	c := repoCache.Do(key{remote, root, localOK}, func() interface{} {
+		repo, err := newRepo(remote, root, localOK)
+		return cached{repo, err}
+	}).(cached)
+
+	return c.repo, c.err
+}
+
 func newRepo(remote, root string, localOK bool) (codehost.Repo, error) {
 	r := &repo{remote: remote, root: root, canArchive: true}
 	if strings.Contains(remote, "://") {
@@ -85,10 +108,12 @@
 }
 
 type repo struct {
-	remote     string
-	local      bool
-	root       string
-	dir        string
+	remote string
+	local  bool
+	root   string
+	dir    string
+
+	mu         sync.Mutex // protects canArchive, some git repo state
 	canArchive bool
 
 	refsOnce sync.Once
@@ -270,6 +295,15 @@
 		return nil, nil, fmt.Errorf("unknown revision %q", rev)
 	}
 
+	// Protect r.canArchive.
+	//
+	// Also protects against multiple goroutines running through the
+	// "progressively fetch more and more" sequence at the same time.
+	// TODO(rsc): Add codehost.LockDir and use it for protecting that
+	// sequence, so that multiple processes don't collide.
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
 	if r.canArchive {
 		// git archive with --remote requires a ref, not a hash.
 		// Proceed only if we know a ref for this hash.