Skip to content

Commit c921157

Browse files
author
Bryan C. Mills
committed
cmd/go/internal/modfetch: remove error return from Lookup
We generally don't care about errors in resolving a repo if the result we're looking for is already in the module cache. Moreover, we can avoid some expense in initializing the repo if all of the methods we plan to call on it hit in the cache — especially when using GOPROXY=direct. This also incidentally fixes a possible (but rare) bug in Download: we had forgotten to reset the downloaded file in case the Zip method returned an error after writing a nonzero number of bytes. For #37438 Change-Id: Ib64f10f763f6d1936536b8e1f7d31ed1b463e955 Reviewed-on: https://go-review.googlesource.com/c/go/+/259158 Trust: Bryan C. Mills <[email protected]> Trust: Jay Conrod <[email protected]> Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Michael Matloob <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent 3a65abf commit c921157

File tree

6 files changed

+87
-87
lines changed

6 files changed

+87
-87
lines changed

src/cmd/go/internal/modfetch/cache.go

+30-40
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"os"
1515
"path/filepath"
1616
"strings"
17+
"sync"
1718

1819
"cmd/go/internal/base"
1920
"cmd/go/internal/cfg"
@@ -155,16 +156,30 @@ func SideLock() (unlock func(), err error) {
155156
type cachingRepo struct {
156157
path string
157158
cache par.Cache // cache for all operations
158-
r Repo
159+
160+
once sync.Once
161+
initRepo func() (Repo, error)
162+
r Repo
159163
}
160164

161-
func newCachingRepo(r Repo) *cachingRepo {
165+
func newCachingRepo(path string, initRepo func() (Repo, error)) *cachingRepo {
162166
return &cachingRepo{
163-
r: r,
164-
path: r.ModulePath(),
167+
path: path,
168+
initRepo: initRepo,
165169
}
166170
}
167171

172+
func (r *cachingRepo) repo() Repo {
173+
r.once.Do(func() {
174+
var err error
175+
r.r, err = r.initRepo()
176+
if err != nil {
177+
r.r = errRepo{r.path, err}
178+
}
179+
})
180+
return r.r
181+
}
182+
168183
func (r *cachingRepo) ModulePath() string {
169184
return r.path
170185
}
@@ -175,7 +190,7 @@ func (r *cachingRepo) Versions(prefix string) ([]string, error) {
175190
err error
176191
}
177192
c := r.cache.Do("versions:"+prefix, func() interface{} {
178-
list, err := r.r.Versions(prefix)
193+
list, err := r.repo().Versions(prefix)
179194
return cached{list, err}
180195
}).(cached)
181196

@@ -197,7 +212,7 @@ func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
197212
return cachedInfo{info, nil}
198213
}
199214

200-
info, err = r.r.Stat(rev)
215+
info, err = r.repo().Stat(rev)
201216
if err == nil {
202217
// If we resolved, say, 1234abcde to v0.0.0-20180604122334-1234abcdef78,
203218
// then save the information under the proper version, for future use.
@@ -224,7 +239,7 @@ func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
224239

225240
func (r *cachingRepo) Latest() (*RevInfo, error) {
226241
c := r.cache.Do("latest:", func() interface{} {
227-
info, err := r.r.Latest()
242+
info, err := r.repo().Latest()
228243

229244
// Save info for likely future Stat call.
230245
if err == nil {
@@ -258,7 +273,7 @@ func (r *cachingRepo) GoMod(version string) ([]byte, error) {
258273
return cached{text, nil}
259274
}
260275

261-
text, err = r.r.GoMod(version)
276+
text, err = r.repo().GoMod(version)
262277
if err == nil {
263278
if err := checkGoMod(r.path, version, text); err != nil {
264279
return cached{text, err}
@@ -277,26 +292,11 @@ func (r *cachingRepo) GoMod(version string) ([]byte, error) {
277292
}
278293

279294
func (r *cachingRepo) Zip(dst io.Writer, version string) error {
280-
return r.r.Zip(dst, version)
281-
}
282-
283-
// Stat is like Lookup(path).Stat(rev) but avoids the
284-
// repository path resolution in Lookup if the result is
285-
// already cached on local disk.
286-
func Stat(proxy, path, rev string) (*RevInfo, error) {
287-
_, info, err := readDiskStat(path, rev)
288-
if err == nil {
289-
return info, nil
290-
}
291-
repo, err := Lookup(proxy, path)
292-
if err != nil {
293-
return nil, err
294-
}
295-
return repo.Stat(rev)
295+
return r.repo().Zip(dst, version)
296296
}
297297

298-
// InfoFile is like Stat but returns the name of the file containing
299-
// the cached information.
298+
// InfoFile is like Lookup(path).Stat(version) but returns the name of the file
299+
// containing the cached information.
300300
func InfoFile(path, version string) (string, error) {
301301
if !semver.IsValid(version) {
302302
return "", fmt.Errorf("invalid version %q", version)
@@ -307,10 +307,7 @@ func InfoFile(path, version string) (string, error) {
307307
}
308308

309309
err := TryProxies(func(proxy string) error {
310-
repo, err := Lookup(proxy, path)
311-
if err == nil {
312-
_, err = repo.Stat(version)
313-
}
310+
_, err := Lookup(proxy, path).Stat(version)
314311
return err
315312
})
316313
if err != nil {
@@ -336,11 +333,7 @@ func GoMod(path, rev string) ([]byte, error) {
336333
rev = info.Version
337334
} else {
338335
err := TryProxies(func(proxy string) error {
339-
repo, err := Lookup(proxy, path)
340-
if err != nil {
341-
return err
342-
}
343-
info, err := repo.Stat(rev)
336+
info, err := Lookup(proxy, path).Stat(rev)
344337
if err == nil {
345338
rev = info.Version
346339
}
@@ -357,11 +350,8 @@ func GoMod(path, rev string) ([]byte, error) {
357350
return data, nil
358351
}
359352

360-
err = TryProxies(func(proxy string) error {
361-
repo, err := Lookup(proxy, path)
362-
if err == nil {
363-
data, err = repo.GoMod(rev)
364-
}
353+
err = TryProxies(func(proxy string) (err error) {
354+
data, err = Lookup(proxy, path).GoMod(rev)
365355
return err
366356
})
367357
return data, err

src/cmd/go/internal/modfetch/coderepo_test.go

+6-22
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ var altVgotests = map[string]string{
6060
type codeRepoTest struct {
6161
vcs string
6262
path string
63-
lookErr string
6463
mpath string
6564
rev string
6665
err string
@@ -332,9 +331,9 @@ var codeRepoTests = []codeRepoTest{
332331
// package in subdirectory - custom domain
333332
// In general we can't reject these definitively in Lookup,
334333
// but gopkg.in is special.
335-
vcs: "git",
336-
path: "gopkg.in/yaml.v2/abc",
337-
lookErr: "invalid module path \"gopkg.in/yaml.v2/abc\"",
334+
vcs: "git",
335+
path: "gopkg.in/yaml.v2/abc",
336+
err: "invalid module path \"gopkg.in/yaml.v2/abc\"",
338337
},
339338
{
340339
// package in subdirectory - github
@@ -440,16 +439,7 @@ func TestCodeRepo(t *testing.T) {
440439
testenv.MustHaveExecPath(t, tt.vcs)
441440
}
442441

443-
repo, err := Lookup("direct", tt.path)
444-
if tt.lookErr != "" {
445-
if err != nil && err.Error() == tt.lookErr {
446-
return
447-
}
448-
t.Errorf("Lookup(%q): %v, want error %q", tt.path, err, tt.lookErr)
449-
}
450-
if err != nil {
451-
t.Fatalf("Lookup(%q): %v", tt.path, err)
452-
}
442+
repo := Lookup("direct", tt.path)
453443

454444
if tt.mpath == "" {
455445
tt.mpath = tt.path
@@ -685,10 +675,7 @@ func TestCodeRepoVersions(t *testing.T) {
685675
testenv.MustHaveExecPath(t, tt.vcs)
686676
}
687677

688-
repo, err := Lookup("direct", tt.path)
689-
if err != nil {
690-
t.Fatalf("Lookup(%q): %v", tt.path, err)
691-
}
678+
repo := Lookup("direct", tt.path)
692679
list, err := repo.Versions(tt.prefix)
693680
if err != nil {
694681
t.Fatalf("Versions(%q): %v", tt.prefix, err)
@@ -763,10 +750,7 @@ func TestLatest(t *testing.T) {
763750
testenv.MustHaveExecPath(t, tt.vcs)
764751
}
765752

766-
repo, err := Lookup("direct", tt.path)
767-
if err != nil {
768-
t.Fatalf("Lookup(%q): %v", tt.path, err)
769-
}
753+
repo := Lookup("direct", tt.path)
770754
info, err := repo.Latest()
771755
if err != nil {
772756
if tt.err != "" {

src/cmd/go/internal/modfetch/fetch.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,28 @@ func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err e
233233
}
234234
}()
235235

236+
var unrecoverableErr error
236237
err = TryProxies(func(proxy string) error {
237-
repo, err := Lookup(proxy, mod.Path)
238+
if unrecoverableErr != nil {
239+
return unrecoverableErr
240+
}
241+
repo := Lookup(proxy, mod.Path)
242+
err := repo.Zip(f, mod.Version)
238243
if err != nil {
239-
return err
244+
// Zip may have partially written to f before failing.
245+
// (Perhaps the server crashed while sending the file?)
246+
// Since we allow fallback on error in some cases, we need to fix up the
247+
// file to be empty again for the next attempt.
248+
if _, err := f.Seek(0, io.SeekStart); err != nil {
249+
unrecoverableErr = err
250+
return err
251+
}
252+
if err := f.Truncate(0); err != nil {
253+
unrecoverableErr = err
254+
return err
255+
}
240256
}
241-
return repo.Zip(f, mod.Version)
257+
return err
242258
})
243259
if err != nil {
244260
return err

src/cmd/go/internal/modfetch/repo.go

+27-11
Original file line numberDiff line numberDiff line change
@@ -188,27 +188,26 @@ type lookupCacheKey struct {
188188
//
189189
// A successful return does not guarantee that the module
190190
// has any defined versions.
191-
func Lookup(proxy, path string) (Repo, error) {
191+
func Lookup(proxy, path string) Repo {
192192
if traceRepo {
193193
defer logCall("Lookup(%q, %q)", proxy, path)()
194194
}
195195

196196
type cached struct {
197-
r Repo
198-
err error
197+
r Repo
199198
}
200199
c := lookupCache.Do(lookupCacheKey{proxy, path}, func() interface{} {
201-
r, err := lookup(proxy, path)
202-
if err == nil {
203-
if traceRepo {
200+
r := newCachingRepo(path, func() (Repo, error) {
201+
r, err := lookup(proxy, path)
202+
if err == nil && traceRepo {
204203
r = newLoggingRepo(r)
205204
}
206-
r = newCachingRepo(r)
207-
}
208-
return cached{r, err}
205+
return r, err
206+
})
207+
return cached{r}
209208
}).(cached)
210209

211-
return c.r, c.err
210+
return c.r
212211
}
213212

214213
// lookup returns the module with the given module path.
@@ -228,7 +227,7 @@ func lookup(proxy, path string) (r Repo, err error) {
228227

229228
switch proxy {
230229
case "off":
231-
return nil, errProxyOff
230+
return errRepo{path, errProxyOff}, nil
232231
case "direct":
233232
return lookupDirect(path)
234233
case "noproxy":
@@ -407,6 +406,23 @@ func (l *loggingRepo) Zip(dst io.Writer, version string) error {
407406
return l.r.Zip(dst, version)
408407
}
409408

409+
// errRepo is a Repo that returns the same error for all operations.
410+
//
411+
// It is useful in conjunction with caching, since cache hits will not attempt
412+
// the prohibited operations.
413+
type errRepo struct {
414+
modulePath string
415+
err error
416+
}
417+
418+
func (r errRepo) ModulePath() string { return r.modulePath }
419+
420+
func (r errRepo) Versions(prefix string) (tags []string, err error) { return nil, r.err }
421+
func (r errRepo) Stat(rev string) (*RevInfo, error) { return nil, r.err }
422+
func (r errRepo) Latest() (*RevInfo, error) { return nil, r.err }
423+
func (r errRepo) GoMod(version string) ([]byte, error) { return nil, r.err }
424+
func (r errRepo) Zip(dst io.Writer, version string) error { return r.err }
425+
410426
// A notExistError is like os.ErrNotExist, but with a custom message
411427
type notExistError struct {
412428
err error

src/cmd/go/internal/modload/mvs.go

+1-5
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,7 @@ func versions(ctx context.Context, path string, allowed AllowedFunc) ([]string,
7777
// so there's no need for us to add extra caching here.
7878
var versions []string
7979
err := modfetch.TryProxies(func(proxy string) error {
80-
repo, err := modfetch.Lookup(proxy, path)
81-
if err != nil {
82-
return err
83-
}
84-
allVersions, err := repo.Versions("")
80+
allVersions, err := modfetch.Lookup(proxy, path).Versions("")
8581
if err != nil {
8682
return err
8783
}

src/cmd/go/internal/modload/query.go

+4-6
Original file line numberDiff line numberDiff line change
@@ -229,14 +229,15 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
229229
// If the identifier is not a canonical semver tag — including if it's a
230230
// semver tag with a +metadata suffix — then modfetch.Stat will populate
231231
// info.Version with a suitable pseudo-version.
232-
info, err := modfetch.Stat(proxy, path, query)
232+
repo := modfetch.Lookup(proxy, path)
233+
info, err := repo.Stat(query)
233234
if err != nil {
234235
queryErr := err
235236
// The full query doesn't correspond to a tag. If it is a semantic version
236237
// with a +metadata suffix, see if there is a tag without that suffix:
237238
// semantic versioning defines them to be equivalent.
238239
if canonicalQuery != "" && query != canonicalQuery {
239-
info, err = modfetch.Stat(proxy, path, canonicalQuery)
240+
info, err = repo.Stat(canonicalQuery)
240241
if err != nil && !errors.Is(err, os.ErrNotExist) {
241242
return info, err
242243
}
@@ -266,10 +267,7 @@ func queryProxy(ctx context.Context, proxy, path, query, current string, allowed
266267
}
267268

268269
// Load versions and execute query.
269-
repo, err := modfetch.Lookup(proxy, path)
270-
if err != nil {
271-
return nil, err
272-
}
270+
repo := modfetch.Lookup(proxy, path)
273271
versions, err := repo.Versions(prefix)
274272
if err != nil {
275273
return nil, err

0 commit comments

Comments
 (0)