]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/go/internal/par: use generic Cache
authorRoger Peppe <rogpeppe@gmail.com>
Mon, 30 Jan 2023 17:27:42 +0000 (17:27 +0000)
committerroger peppe <rogpeppe@gmail.com>
Fri, 3 Feb 2023 09:56:24 +0000 (09:56 +0000)
Using generics here makes the code easier to understand,
as the contract is clearly specified. It also makes the
code a little more concise, as it's easy to write a wrapper
for the cache that adds an error value, meaning that
a bunch of auxilliary types no longer need to be defined
for this common case.

The load.cachingRepo code has been changed to use a separate
cache for each key-value type combination, which seems a bit less
sleazy, but might have some knock-on effect on memory usage,
and could easily be changed back if desired.

Because there's no longer an unambiguous way to find out
whether there's an entry in the cache, the Cache.Get method
now returns a bool as well as the value itself.

Change-Id: I28443125bab0b3720cc95d750e72d28e9b96257d
Reviewed-on: https://go-review.googlesource.com/c/go/+/463843
Reviewed-by: Bryan Mills <bcmills@google.com>
Run-TryBot: roger peppe <rogpeppe@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
17 files changed:
src/cmd/go/internal/load/pkg.go
src/cmd/go/internal/modfetch/cache.go
src/cmd/go/internal/modfetch/codehost/git.go
src/cmd/go/internal/modfetch/codehost/vcs.go
src/cmd/go/internal/modfetch/fetch.go
src/cmd/go/internal/modfetch/repo.go
src/cmd/go/internal/modget/get.go
src/cmd/go/internal/modindex/read.go
src/cmd/go/internal/modindex/scan.go
src/cmd/go/internal/modload/build.go
src/cmd/go/internal/modload/buildlist.go
src/cmd/go/internal/modload/import.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/modload/modfile.go
src/cmd/go/internal/par/work.go
src/cmd/go/internal/par/work_test.go
src/cmd/go/proxy_test.go

index 9e6b3ebcbdaef0d67533e1b208c335fec6ed91bc..7c0c104883100d2f8d6a18f342617e7f5f6e6648 100644 (file)
@@ -612,11 +612,11 @@ func ClearPackageCachePartial(args []string) {
                        delete(packageCache, arg)
                }
        }
-       resolvedImportCache.DeleteIf(func(key any) bool {
-               return shouldDelete[key.(importSpec).path]
+       resolvedImportCache.DeleteIf(func(key importSpec) bool {
+               return shouldDelete[key.path]
        })
-       packageDataCache.DeleteIf(func(key any) bool {
-               return shouldDelete[key.(string)]
+       packageDataCache.DeleteIf(func(key string) bool {
+               return shouldDelete[key]
        })
 }
 
@@ -628,8 +628,8 @@ func ReloadPackageNoFlags(arg string, stk *ImportStack) *Package {
        p := packageCache[arg]
        if p != nil {
                delete(packageCache, arg)
-               resolvedImportCache.DeleteIf(func(key any) bool {
-                       return key.(importSpec).path == p.ImportPath
+               resolvedImportCache.DeleteIf(func(key importSpec) bool {
+                       return key.path == p.ImportPath
                })
                packageDataCache.Delete(p.ImportPath)
        }
@@ -846,7 +846,7 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo
                parentIsStd: parentIsStd,
                mode:        mode,
        }
-       r := resolvedImportCache.Do(importKey, func() any {
+       r := resolvedImportCache.Do(importKey, func() resolvedImport {
                var r resolvedImport
                if cfg.ModulesEnabled {
                        r.dir, r.path, r.err = modload.Lookup(parentPath, parentIsStd, path)
@@ -866,16 +866,19 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo
                        r.path = path
                }
                return r
-       }).(resolvedImport)
+       })
        // Invariant: r.path is set to the resolved import path. If the path cannot
        // be resolved, r.path is set to path, the source import path.
        // r.path is never empty.
 
        // Load the package from its directory. If we already found the package's
        // directory when resolving its import path, use that.
-       data := packageDataCache.Do(r.path, func() any {
+       p, err := packageDataCache.Do(r.path, func() (*build.Package, error) {
                loaded = true
-               var data packageData
+               var data struct {
+                       p   *build.Package
+                       err error
+               }
                if r.dir != "" {
                        var buildMode build.ImportMode
                        buildContext := cfg.BuildContext
@@ -961,10 +964,10 @@ func loadPackageData(ctx context.Context, path, parentPath, parentDir, parentRoo
                        !strings.Contains(path, "/vendor/") && !strings.HasPrefix(path, "vendor/") {
                        data.err = fmt.Errorf("code in directory %s expects import %q", data.p.Dir, data.p.ImportComment)
                }
-               return data
-       }).(packageData)
+               return data.p, data.err
+       })
 
-       return data.p, loaded, data.err
+       return p, loaded, err
 }
 
 // importSpec describes an import declaration in source code. It is used as a
@@ -984,20 +987,11 @@ type resolvedImport struct {
        err       error
 }
 
-// packageData holds information loaded from a package. It is the value type
-// in packageDataCache.
-type packageData struct {
-       p   *build.Package
-       err error
-}
-
-// resolvedImportCache maps import strings (importSpec) to canonical package names
-// (resolvedImport).
-var resolvedImportCache par.Cache
+// resolvedImportCache maps import strings to canonical package names.
+var resolvedImportCache par.Cache[importSpec, resolvedImport]
 
-// packageDataCache maps canonical package names (string) to package metadata
-// (packageData).
-var packageDataCache par.Cache
+// packageDataCache maps canonical package names (string) to package metadata.
+var packageDataCache par.ErrCache[string, *build.Package]
 
 // preloadWorkerCount is the number of concurrent goroutines that can load
 // packages. Experimentally, there are diminishing returns with more than
@@ -1109,13 +1103,13 @@ func cleanImport(path string) string {
        return path
 }
 
-var isDirCache par.Cache
+var isDirCache par.Cache[string, bool]
 
 func isDir(path string) bool {
-       return isDirCache.Do(path, func() any {
+       return isDirCache.Do(path, func() bool {
                fi, err := fsys.Stat(path)
                return err == nil && fi.IsDir()
-       }).(bool)
+       })
 }
 
 // ResolveImportPath returns the true meaning of path when it appears in parent.
@@ -1236,12 +1230,12 @@ func vendoredImportPath(path, parentPath, parentDir, parentRoot string) (found s
 
 var (
        modulePrefix   = []byte("\nmodule ")
-       goModPathCache par.Cache
+       goModPathCache par.Cache[string, string]
 )
 
 // goModPath returns the module path in the go.mod in dir, if any.
 func goModPath(dir string) (path string) {
-       return goModPathCache.Do(dir, func() any {
+       return goModPathCache.Do(dir, func() string {
                data, err := os.ReadFile(filepath.Join(dir, "go.mod"))
                if err != nil {
                        return ""
@@ -1277,7 +1271,7 @@ func goModPath(dir string) (path string) {
                        path = s
                }
                return path
-       }).(string)
+       })
 }
 
 // findVersionElement returns the slice indices of the final version element /vN in path.
@@ -2264,8 +2258,8 @@ func (p *Package) collectDeps() {
 }
 
 // vcsStatusCache maps repository directories (string)
-// to their VCS information (vcsStatusError).
-var vcsStatusCache par.Cache
+// to their VCS information.
+var vcsStatusCache par.ErrCache[string, vcs.Status]
 
 // setBuildInfo gathers build information, formats it as a string to be
 // embedded in the binary, then sets p.Internal.BuildInfo to that string.
@@ -2517,19 +2511,13 @@ func (p *Package) setBuildInfo(autoVCS bool) {
                        goto omitVCS
                }
 
-               type vcsStatusError struct {
-                       Status vcs.Status
-                       Err    error
-               }
-               cached := vcsStatusCache.Do(repoDir, func() any {
-                       st, err := vcsCmd.Status(vcsCmd, repoDir)
-                       return vcsStatusError{st, err}
-               }).(vcsStatusError)
-               if err := cached.Err; err != nil {
+               st, err := vcsStatusCache.Do(repoDir, func() (vcs.Status, error) {
+                       return vcsCmd.Status(vcsCmd, repoDir)
+               })
+               if err != nil {
                        setVCSError(err)
                        return
                }
-               st := cached.Status
 
                appendSetting("vcs", vcsCmd.Cmd)
                if st.Revision != "" {
index 928eb1f70e07e4216989ec529a3a78f660c544e8..8217450b57c81fc6f72a1777e27baf70ed75c413 100644 (file)
@@ -169,8 +169,11 @@ func SideLock() (unlock func(), err error) {
 // (so that it can be returned from Lookup multiple times).
 // It serializes calls to the underlying Repo.
 type cachingRepo struct {
-       path  string
-       cache par.Cache // cache for all operations
+       path          string
+       versionsCache par.ErrCache[string, *Versions]
+       statCache     par.ErrCache[string, *RevInfo]
+       latestCache   par.ErrCache[struct{}, *RevInfo]
+       gomodCache    par.ErrCache[string, []byte]
 
        once     sync.Once
        initRepo func() (Repo, error)
@@ -204,23 +207,17 @@ func (r *cachingRepo) ModulePath() string {
 }
 
 func (r *cachingRepo) Versions(prefix string) (*Versions, error) {
-       type cached struct {
-               v   *Versions
-               err error
-       }
-       c := r.cache.Do("versions:"+prefix, func() any {
-               v, err := r.repo().Versions(prefix)
-               return cached{v, err}
-       }).(cached)
+       v, err := r.versionsCache.Do(prefix, func() (*Versions, error) {
+               return r.repo().Versions(prefix)
+       })
 
-       if c.err != nil {
-               return nil, c.err
-       }
-       v := &Versions{
-               Origin: c.v.Origin,
-               List:   append([]string(nil), c.v.List...),
+       if err != nil {
+               return nil, err
        }
-       return v, nil
+       return &Versions{
+               Origin: v.Origin,
+               List:   append([]string(nil), v.List...),
+       }, nil
 }
 
 type cachedInfo struct {
@@ -229,10 +226,10 @@ type cachedInfo struct {
 }
 
 func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
-       c := r.cache.Do("stat:"+rev, func() any {
+       info, err := r.statCache.Do(rev, func() (*RevInfo, error) {
                file, info, err := readDiskStat(r.path, rev)
                if err == nil {
-                       return cachedInfo{info, nil}
+                       return info, err
                }
 
                info, err = r.repo().Stat(rev)
@@ -241,8 +238,8 @@ func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
                        // then save the information under the proper version, for future use.
                        if info.Version != rev {
                                file, _ = CachePath(module.Version{Path: r.path, Version: info.Version}, "info")
-                               r.cache.Do("stat:"+info.Version, func() any {
-                                       return cachedInfo{info, err}
+                               r.statCache.Do(info.Version, func() (*RevInfo, error) {
+                                       return info, nil
                                })
                        }
 
@@ -250,70 +247,61 @@ func (r *cachingRepo) Stat(rev string) (*RevInfo, error) {
                                fmt.Fprintf(os.Stderr, "go: writing stat cache: %v\n", err)
                        }
                }
-               return cachedInfo{info, err}
-       }).(cachedInfo)
-
-       info := c.info
+               return info, err
+       })
        if info != nil {
                copy := *info
                info = &copy
        }
-       return info, c.err
+       return info, err
 }
 
 func (r *cachingRepo) Latest() (*RevInfo, error) {
-       c := r.cache.Do("latest:", func() any {
+       info, err := r.latestCache.Do(struct{}{}, func() (*RevInfo, error) {
                info, err := r.repo().Latest()
 
                // Save info for likely future Stat call.
                if err == nil {
-                       r.cache.Do("stat:"+info.Version, func() any {
-                               return cachedInfo{info, err}
+                       r.statCache.Do(info.Version, func() (*RevInfo, error) {
+                               return info, nil
                        })
                        if file, _, err := readDiskStat(r.path, info.Version); err != nil {
                                writeDiskStat(file, info)
                        }
                }
 
-               return cachedInfo{info, err}
-       }).(cachedInfo)
-
-       info := c.info
+               return info, err
+       })
        if info != nil {
                copy := *info
                info = &copy
        }
-       return info, c.err
+       return info, err
 }
 
 func (r *cachingRepo) GoMod(version string) ([]byte, error) {
-       type cached struct {
-               text []byte
-               err  error
-       }
-       c := r.cache.Do("gomod:"+version, func() any {
+       text, err := r.gomodCache.Do(version, func() ([]byte, error) {
                file, text, err := readDiskGoMod(r.path, version)
                if err == nil {
                        // Note: readDiskGoMod already called checkGoMod.
-                       return cached{text, nil}
+                       return text, nil
                }
 
                text, err = r.repo().GoMod(version)
                if err == nil {
                        if err := checkGoMod(r.path, version, text); err != nil {
-                               return cached{text, err}
+                               return text, err
                        }
                        if err := writeDiskGoMod(file, text); err != nil {
                                fmt.Fprintf(os.Stderr, "go: writing go.mod cache: %v\n", err)
                        }
                }
-               return cached{text, err}
-       }).(cached)
-
-       if c.err != nil {
-               return nil, c.err
+               return text, err
+       })
+       if err != nil {
+               return nil, err
        }
-       return append([]byte(nil), c.text...), nil
+       return append([]byte(nil), text...), nil
 }
 
 func (r *cachingRepo) Zip(dst io.Writer, version string) error {
index 127ad264afa76994f64770265a4ff7df33fd08f2..93c39cf3daf9dd1535c00239ee826ca7654ed928 100644 (file)
@@ -46,24 +46,17 @@ func (notExistError) Is(err error) bool { return err == fs.ErrNotExist }
 
 const gitWorkDirType = "git3"
 
-var gitRepoCache par.Cache
+var gitRepoCache par.ErrCache[gitCacheKey, Repo]
 
-func newGitRepoCached(remote string, localOK bool) (Repo, error) {
-       type key struct {
-               remote  string
-               localOK bool
-       }
-       type cached struct {
-               repo Repo
-               err  error
-       }
-
-       c := gitRepoCache.Do(key{remote, localOK}, func() any {
-               repo, err := newGitRepo(remote, localOK)
-               return cached{repo, err}
-       }).(cached)
+type gitCacheKey struct {
+       remote  string
+       localOK bool
+}
 
-       return c.repo, c.err
+func newGitRepoCached(remote string, localOK bool) (Repo, error) {
+       return gitRepoCache.Do(gitCacheKey{remote, localOK}, func() (Repo, error) {
+               return newGitRepo(remote, localOK)
+       })
 }
 
 func newGitRepo(remote string, localOK bool) (Repo, error) {
@@ -132,7 +125,7 @@ type gitRepo struct {
 
        fetchLevel int
 
-       statCache par.Cache
+       statCache par.ErrCache[string, *RevInfo]
 
        refsOnce sync.Once
        // refs maps branch and tag refs (e.g., "HEAD", "refs/heads/master")
@@ -637,15 +630,9 @@ func (r *gitRepo) Stat(rev string) (*RevInfo, error) {
        if rev == "latest" {
                return r.Latest()
        }
-       type cached struct {
-               info *RevInfo
-               err  error
-       }
-       c := r.statCache.Do(rev, func() any {
-               info, err := r.stat(rev)
-               return cached{info, err}
-       }).(cached)
-       return c.info, c.err
+       return r.statCache.Do(rev, func() (*RevInfo, error) {
+               return r.stat(rev)
+       })
 }
 
 func (r *gitRepo) ReadFile(rev, file string, maxSize int64) ([]byte, error) {
index 300a23c6ac5387477527039950c40fc17676f820..9628a8c7af38bf0a941fa1ec48d6dfac6fd7f014 100644 (file)
@@ -44,27 +44,22 @@ func vcsErrorf(format string, a ...any) error {
        return &VCSError{Err: fmt.Errorf(format, a...)}
 }
 
+type vcsCacheKey struct {
+       vcs    string
+       remote string
+}
+
 func NewRepo(vcs, remote string) (Repo, error) {
-       type key struct {
-               vcs    string
-               remote string
-       }
-       type cached struct {
-               repo Repo
-               err  error
-       }
-       c := vcsRepoCache.Do(key{vcs, remote}, func() any {
+       return vcsRepoCache.Do(vcsCacheKey{vcs, remote}, func() (Repo, error) {
                repo, err := newVCSRepo(vcs, remote)
                if err != nil {
-                       err = &VCSError{err}
+                       return nil, &VCSError{err}
                }
-               return cached{repo, err}
-       }).(cached)
-
-       return c.repo, c.err
+               return repo, nil
+       })
 }
 
-var vcsRepoCache par.Cache
+var vcsRepoCache par.ErrCache[vcsCacheKey, Repo]
 
 type vcsRepo struct {
        mu lockedfile.Mutex // protects all commands, so we don't have to decide which are safe on a per-VCS basis
index dfe5da677e32eee963ef595949a2af4293d9d0d1..c9502ca4326fc48b5fe55e6587ad09c1dd362779 100644 (file)
@@ -34,7 +34,7 @@ import (
        modzip "golang.org/x/mod/zip"
 )
 
-var downloadCache par.Cache
+var downloadCache par.ErrCache[module.Version, string] // version → directory
 
 // Download downloads the specific module version to the
 // local download cache and returns the name of the directory
@@ -45,19 +45,14 @@ func Download(ctx context.Context, mod module.Version) (dir string, err error) {
        }
 
        // The par.Cache here avoids duplicate work.
-       type cached struct {
-               dir string
-               err error
-       }
-       c := downloadCache.Do(mod, func() any {
+       return downloadCache.Do(mod, func() (string, error) {
                dir, err := download(ctx, mod)
                if err != nil {
-                       return cached{"", err}
+                       return "", err
                }
                checkMod(mod)
-               return cached{dir, nil}
-       }).(cached)
-       return c.dir, c.err
+               return dir, nil
+       })
 }
 
 func download(ctx context.Context, mod module.Version) (dir string, err error) {
@@ -156,27 +151,23 @@ func download(ctx context.Context, mod module.Version) (dir string, err error) {
        return dir, nil
 }
 
-var downloadZipCache par.Cache
+var downloadZipCache par.ErrCache[module.Version, string]
 
 // DownloadZip downloads the specific module version to the
 // local zip cache and returns the name of the zip file.
 func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err error) {
        // The par.Cache here avoids duplicate work.
-       type cached struct {
-               zipfile string
-               err     error
-       }
-       c := downloadZipCache.Do(mod, func() any {
+       return downloadZipCache.Do(mod, func() (string, error) {
                zipfile, err := CachePath(mod, "zip")
                if err != nil {
-                       return cached{"", err}
+                       return "", err
                }
                ziphashfile := zipfile + "hash"
 
                // Return without locking if the zip and ziphash files exist.
                if _, err := os.Stat(zipfile); err == nil {
                        if _, err := os.Stat(ziphashfile); err == nil {
-                               return cached{zipfile, nil}
+                               return zipfile, nil
                        }
                }
 
@@ -186,16 +177,15 @@ func DownloadZip(ctx context.Context, mod module.Version) (zipfile string, err e
                }
                unlock, err := lockVersion(mod)
                if err != nil {
-                       return cached{"", err}
+                       return "", err
                }
                defer unlock()
 
                if err := downloadZip(ctx, mod, zipfile); err != nil {
-                       return cached{"", err}
+                       return "", err
                }
-               return cached{zipfile, nil}
-       }).(cached)
-       return c.zipfile, c.err
+               return zipfile, nil
+       })
 }
 
 func downloadZip(ctx context.Context, mod module.Version, zipfile string) (err error) {
@@ -416,8 +406,8 @@ func Reset() {
        // Uses of lookupCache and downloadCache both can call checkModSum,
        // which in turn sets the used bit on goSum.status for modules.
        // Reset them so used can be computed properly.
-       lookupCache = par.Cache{}
-       downloadCache = par.Cache{}
+       lookupCache = par.Cache[lookupCacheKey, Repo]{}
+       downloadCache = par.ErrCache[module.Version, string]{}
 
        // Clear all fields on goSum. It will be initialized later
        goSum.mu.Lock()
index d4c57bb300afbaf37aaa808df3859d3f61cd443a..68993c4ce41221fa8ad8b4578bce475851fc62bd 100644 (file)
@@ -185,7 +185,7 @@ type RevInfo struct {
 // To avoid version control access except when absolutely necessary,
 // Lookup does not attempt to connect to the repository itself.
 
-var lookupCache par.Cache
+var lookupCache par.Cache[lookupCacheKey, Repo]
 
 type lookupCacheKey struct {
        proxy, path string
@@ -208,21 +208,15 @@ func Lookup(proxy, path string) Repo {
                defer logCall("Lookup(%q, %q)", proxy, path)()
        }
 
-       type cached struct {
-               r Repo
-       }
-       c := lookupCache.Do(lookupCacheKey{proxy, path}, func() any {
-               r := newCachingRepo(path, func() (Repo, error) {
+       return lookupCache.Do(lookupCacheKey{proxy, path}, func() Repo {
+               return newCachingRepo(path, func() (Repo, error) {
                        r, err := lookup(proxy, path)
                        if err == nil && traceRepo {
                                r = newLoggingRepo(r)
                        }
                        return r, err
                })
-               return cached{r}
-       }).(cached)
-
-       return c.r
+       })
 }
 
 // lookup returns the module with the given module path.
index d0beff548529cf47c5ab38e8f99bc8576b74c123..da4d004af2d8f1d515b7a9a0d889962b35e26f73 100644 (file)
@@ -447,7 +447,7 @@ type resolver struct {
 
        work *par.Queue
 
-       matchInModuleCache par.Cache
+       matchInModuleCache par.ErrCache[matchInModuleKey, []string]
 }
 
 type versionReason struct {
@@ -455,6 +455,11 @@ type versionReason struct {
        reason  *query
 }
 
+type matchInModuleKey struct {
+       pattern string
+       m       module.Version
+}
+
 func newResolver(ctx context.Context, queries []*query) *resolver {
        // LoadModGraph also sets modload.Target, which is needed by various resolver
        // methods.
@@ -592,24 +597,13 @@ func (r *resolver) checkAllowedOr(requested string, selected func(string) string
 
 // matchInModule is a caching wrapper around modload.MatchInModule.
 func (r *resolver) matchInModule(ctx context.Context, pattern string, m module.Version) (packages []string, err error) {
-       type key struct {
-               pattern string
-               m       module.Version
-       }
-       type entry struct {
-               packages []string
-               err      error
-       }
-
-       e := r.matchInModuleCache.Do(key{pattern, m}, func() any {
+       return r.matchInModuleCache.Do(matchInModuleKey{pattern, m}, func() ([]string, error) {
                match := modload.MatchInModule(ctx, pattern, m, imports.AnyTags())
                if len(match.Errs) > 0 {
-                       return entry{match.Pkgs, match.Errs[0]}
+                       return match.Pkgs, match.Errs[0]
                }
-               return entry{match.Pkgs, nil}
-       }).(entry)
-
-       return e.packages, e.err
+               return match.Pkgs, nil
+       })
 }
 
 // queryNone adds a candidate set to q for each module matching q.pattern.
index 54567bee4166ecc26f65ddd0ae778141cc90c3e5..314b84d4926159ca281f908852c2ebb132d1bbf0 100644 (file)
@@ -117,8 +117,6 @@ func dirHash(modroot, pkgdir string) (cache.ActionID, error) {
        return h.Sum(), nil
 }
 
-var modrootCache par.Cache
-
 var ErrNotIndexed = errors.New("not in module index")
 
 var (
@@ -168,21 +166,17 @@ func GetModule(modroot string) (*Module, error) {
        return openIndexModule(modroot, true)
 }
 
-var mcache par.Cache
+var mcache par.ErrCache[string, *Module]
 
 // openIndexModule returns the module index for modPath.
 // It will return ErrNotIndexed if the module can not be read
 // using the index because it contains symlinks.
 func openIndexModule(modroot string, ismodcache bool) (*Module, error) {
-       type result struct {
-               mi  *Module
-               err error
-       }
-       r := mcache.Do(modroot, func() any {
+       return mcache.Do(modroot, func() (*Module, error) {
                fsys.Trace("openIndexModule", modroot)
                id, err := moduleHash(modroot, ismodcache)
                if err != nil {
-                       return result{nil, err}
+                       return nil, err
                }
                data, _, err := cache.Default().GetMmap(id)
                if err != nil {
@@ -190,33 +184,28 @@ func openIndexModule(modroot string, ismodcache bool) (*Module, error) {
                        // the index because the module hasn't been indexed yet.
                        data, err = indexModule(modroot)
                        if err != nil {
-                               return result{nil, err}
+                               return nil, err
                        }
                        if err = cache.Default().PutBytes(id, data); err != nil {
-                               return result{nil, err}
+                               return nil, err
                        }
                }
                mi, err := fromBytes(modroot, data)
                if err != nil {
-                       return result{nil, err}
+                       return nil, err
                }
-               return result{mi, nil}
-       }).(result)
-       return r.mi, r.err
+               return mi, nil
+       })
 }
 
-var pcache par.Cache
+var pcache par.ErrCache[[2]string, *IndexPackage]
 
 func openIndexPackage(modroot, pkgdir string) (*IndexPackage, error) {
-       type result struct {
-               pkg *IndexPackage
-               err error
-       }
-       r := pcache.Do([2]string{modroot, pkgdir}, func() any {
+       return pcache.Do([2]string{modroot, pkgdir}, func() (*IndexPackage, error) {
                fsys.Trace("openIndexPackage", pkgdir)
                id, err := dirHash(modroot, pkgdir)
                if err != nil {
-                       return result{nil, err}
+                       return nil, err
                }
                data, _, err := cache.Default().GetMmap(id)
                if err != nil {
@@ -224,16 +213,15 @@ func openIndexPackage(modroot, pkgdir string) (*IndexPackage, error) {
                        // the index because the package hasn't been indexed yet.
                        data = indexPackage(modroot, pkgdir)
                        if err = cache.Default().PutBytes(id, data); err != nil {
-                               return result{nil, err}
+                               return nil, err
                        }
                }
                pkg, err := packageFromBytes(modroot, data)
                if err != nil {
-                       return result{nil, err}
+                       return nil, err
                }
-               return result{pkg, nil}
-       }).(result)
-       return r.pkg, r.err
+               return pkg, nil
+       })
 }
 
 var errCorrupt = errors.New("corrupt index")
index 60197898a026fe47674b600901f15d080d7237ad..0c9a23c64569e8a89df0307b7abdac0354a7c77f 100644 (file)
@@ -7,7 +7,6 @@ package modindex
 import (
        "cmd/go/internal/base"
        "cmd/go/internal/fsys"
-       "cmd/go/internal/par"
        "cmd/go/internal/str"
        "encoding/json"
        "errors"
@@ -172,8 +171,6 @@ type embed struct {
        position token.Position
 }
 
-var pkgcache par.Cache // for packages not in modcache
-
 // importRaw fills the rawPackage from the package files in srcDir.
 // dir is the package's path relative to the modroot.
 func importRaw(modroot, reldir string) *rawPackage {
index 30b248e65a361f753e98c4373d889cc9bf3826ee..d1149a54833c11d7f372fbf034a650e324c237b6 100644 (file)
@@ -417,7 +417,7 @@ func moduleInfo(ctx context.Context, rs *Requirements, m module.Version, mode Li
 // If the package was loaded, its containing module and true are returned.
 // Otherwise, module.Version{} and false are returned.
 func findModule(ld *loader, path string) (module.Version, bool) {
-       if pkg, ok := ld.pkgCache.Get(path).(*loadPkg); ok {
+       if pkg, ok := ld.pkgCache.Get(path); ok {
                return pkg.mod, pkg.mod != module.Version{}
        }
        return module.Version{}, false
index fddcdb6b5d82e45e7fecc08e24f2c5aee1636d3b..005f306ff409664be44123843dfbb8976aa70618 100644 (file)
@@ -255,19 +255,12 @@ func (rs *Requirements) IsDirect(path string) bool {
 // transitive dependencies of non-root (implicit) dependencies.
 type ModuleGraph struct {
        g         *mvs.Graph
-       loadCache par.Cache // module.Version → summaryError
+       loadCache par.ErrCache[module.Version, *modFileSummary]
 
        buildListOnce sync.Once
        buildList     []module.Version
 }
 
-// A summaryError is either a non-nil modFileSummary or a non-nil error
-// encountered while reading or parsing that summary.
-type summaryError struct {
-       summary *modFileSummary
-       err     error
-}
-
 var readModGraphDebugOnce sync.Once
 
 // readModGraph reads and returns the module dependency graph starting at the
@@ -322,7 +315,7 @@ func readModGraph(ctx context.Context, pruning modPruning, roots []module.Versio
        // It does not load the transitive requirements of m even if the go version in
        // m's go.mod file indicates that it supports graph pruning.
        loadOne := func(m module.Version) (*modFileSummary, error) {
-               cached := mg.loadCache.Do(m, func() any {
+               return mg.loadCache.Do(m, func() (*modFileSummary, error) {
                        summary, err := goModSummary(m)
 
                        mu.Lock()
@@ -333,10 +326,8 @@ func readModGraph(ctx context.Context, pruning modPruning, roots []module.Versio
                        }
                        mu.Unlock()
 
-                       return summaryError{summary, err}
-               }).(summaryError)
-
-               return cached.summary, cached.err
+                       return summary, err
+               })
        }
 
        var enqueue func(m module.Version, pruning modPruning)
@@ -473,11 +464,11 @@ func (mg *ModuleGraph) BuildList() []module.Version {
 
 func (mg *ModuleGraph) findError() error {
        errStack := mg.g.FindPath(func(m module.Version) bool {
-               cached := mg.loadCache.Get(m)
-               return cached != nil && cached.(summaryError).err != nil
+               _, err := mg.loadCache.Get(m)
+               return err != nil && err != par.ErrCacheEntryNotFound
        })
        if len(errStack) > 0 {
-               err := mg.loadCache.Get(errStack[len(errStack)-1]).(summaryError).err
+               _, err := mg.loadCache.Get(errStack[len(errStack)-1])
                var noUpgrade func(from, to module.Version) bool
                return mvs.NewBuildListError(err, errStack, noUpgrade)
        }
index 2815ba91762be574bc456269be4171a045cc1ef1..90f2a7401aa9b33e40f3a83be87098cffface0fb 100644 (file)
@@ -614,15 +614,10 @@ func maybeInModule(path, mpath string) bool {
 }
 
 var (
-       haveGoModCache   par.Cache // dir → bool
-       haveGoFilesCache par.Cache // dir → goFilesEntry
+       haveGoModCache   par.Cache[string, bool]    // dir → bool
+       haveGoFilesCache par.ErrCache[string, bool] // dir → haveGoFiles
 )
 
-type goFilesEntry struct {
-       haveGoFiles bool
-       err         error
-}
-
 // dirInModule locates the directory that would hold the package named by the given path,
 // if it were in the module with module path mpath and root mdir.
 // If path is syntactically not within mpath,
@@ -655,10 +650,10 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
        // (the main module, and any directory trees pointed at by replace directives).
        if isLocal {
                for d := dir; d != mdir && len(d) > len(mdir); {
-                       haveGoMod := haveGoModCache.Do(d, func() any {
+                       haveGoMod := haveGoModCache.Do(d, func() bool {
                                fi, err := fsys.Stat(filepath.Join(d, "go.mod"))
                                return err == nil && !fi.IsDir()
-                       }).(bool)
+                       })
 
                        if haveGoMod {
                                return "", false, nil
@@ -678,21 +673,19 @@ func dirInModule(path, mpath, mdir string, isLocal bool) (dir string, haveGoFile
        // Are there Go source files in the directory?
        // We don't care about build tags, not even "+build ignore".
        // We're just looking for a plausible directory.
-       res := haveGoFilesCache.Do(dir, func() any {
+       haveGoFiles, err = haveGoFilesCache.Do(dir, func() (bool, error) {
                // modindex.GetPackage will return ErrNotIndexed for any directories which
                // are reached through a symlink, so that they will be handled by
                // fsys.IsDirWithGoFiles below.
                if ip, err := modindex.GetPackage(mdir, dir); err == nil {
-                       isDirWithGoFiles, err := ip.IsDirWithGoFiles()
-                       return goFilesEntry{isDirWithGoFiles, err}
+                       return ip.IsDirWithGoFiles()
                } else if !errors.Is(err, modindex.ErrNotIndexed) {
-                       return goFilesEntry{err: err}
+                       return false, err
                }
-               ok, err := fsys.IsDirWithGoFiles(dir)
-               return goFilesEntry{haveGoFiles: ok, err: err}
-       }).(goFilesEntry)
+               return fsys.IsDirWithGoFiles(dir)
+       })
 
-       return dir, res.haveGoFiles, res.err
+       return dir, haveGoFiles, err
 }
 
 // fetch downloads the given module (or its replacement)
index f450ced299a29ca013fcb74dfa85b650426490e6..405b7935e025dc99f4a76efe8666d356fbd5aca0 100644 (file)
@@ -772,7 +772,7 @@ func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path s
 
 // PackageModule returns the module providing the package named by the import path.
 func PackageModule(path string) module.Version {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, ok := loaded.pkgCache.Get(path)
        if !ok {
                return module.Version{}
        }
@@ -791,7 +791,7 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str
        if parentIsStd {
                path = loaded.stdVendor(parentPath, path)
        }
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, ok := loaded.pkgCache.Get(path)
        if !ok {
                // The loader should have found all the relevant paths.
                // There are a few exceptions, though:
@@ -827,7 +827,7 @@ type loader struct {
 
        // reset on each iteration
        roots    []*loadPkg
-       pkgCache *par.Cache // package path (string) → *loadPkg
+       pkgCache *par.Cache[string, *loadPkg]
        pkgs     []*loadPkg // transitive closure of loaded packages and tests; populated in buildStacks
 }
 
@@ -850,7 +850,7 @@ func (ld *loader) reset() {
        }
 
        ld.roots = nil
-       ld.pkgCache = new(par.Cache)
+       ld.pkgCache = new(par.Cache[string, *loadPkg])
        ld.pkgs = nil
 }
 
@@ -1504,7 +1504,7 @@ func (ld *loader) pkg(ctx context.Context, path string, flags loadPkgFlags) *loa
                panic("internal error: (*loader).pkg called with pkgImportsLoaded flag set")
        }
 
-       pkg := ld.pkgCache.Do(path, func() any {
+       pkg := ld.pkgCache.Do(path, func() *loadPkg {
                pkg := &loadPkg{
                        path: path,
                }
@@ -1512,7 +1512,7 @@ func (ld *loader) pkg(ctx context.Context, path string, flags loadPkgFlags) *loa
 
                ld.work.Add(func() { ld.load(ctx, pkg) })
                return pkg
-       }).(*loadPkg)
+       })
 
        ld.applyPkgFlags(ctx, pkg, flags)
        return pkg
@@ -2214,7 +2214,7 @@ func (pkg *loadPkg) why() string {
 // If there is no reason for the package to be in the current build,
 // Why returns an empty string.
 func Why(path string) string {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, ok := loaded.pkgCache.Get(path)
        if !ok {
                return ""
        }
@@ -2226,7 +2226,7 @@ func Why(path string) string {
 // WhyDepth returns 0.
 func WhyDepth(path string) int {
        n := 0
-       pkg, _ := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, _ := loaded.pkgCache.Get(path)
        for p := pkg; p != nil; p = p.stack {
                n++
        }
index 75c278a7dfdb5f39dc196f22dd14bf0b21d5c6c2..0e42292c015621f6c10b7c0a83513d57e936b56a 100644 (file)
@@ -659,23 +659,15 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
        if m.Path == "" && MainModules.Contains(m.Path) {
                panic("internal error: rawGoModSummary called on the Target module")
        }
-
-       type key struct {
-               m module.Version
-       }
-       type cached struct {
-               summary *modFileSummary
-               err     error
-       }
-       c := rawGoModSummaryCache.Do(key{m}, func() any {
+       return rawGoModSummaryCache.Do(m, func() (*modFileSummary, error) {
                summary := new(modFileSummary)
                name, data, err := rawGoModData(m)
                if err != nil {
-                       return cached{nil, err}
+                       return nil, err
                }
                f, err := modfile.ParseLax(name, data, nil)
                if err != nil {
-                       return cached{nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))}
+                       return nil, module.VersionError(m, fmt.Errorf("parsing %s: %v", base.ShortPath(name), err))
                }
                if f.Module != nil {
                        summary.module = f.Module.Mod
@@ -704,13 +696,11 @@ func rawGoModSummary(m module.Version) (*modFileSummary, error) {
                        }
                }
 
-               return cached{summary, nil}
-       }).(cached)
-
-       return c.summary, c.err
+               return summary, nil
+       })
 }
 
-var rawGoModSummaryCache par.Cache // module.Version → rawGoModSummary result
+var rawGoModSummaryCache par.ErrCache[module.Version, *modFileSummary]
 
 // rawGoModData returns the content of the go.mod file for module m, ignoring
 // all replacements that may apply to m.
@@ -765,18 +755,14 @@ func rawGoModData(m module.Version) (name string, data []byte, err error) {
 // If the queried latest version is replaced,
 // queryLatestVersionIgnoringRetractions returns the replacement.
 func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (latest module.Version, err error) {
-       type entry struct {
-               latest module.Version
-               err    error
-       }
-       e := latestVersionIgnoringRetractionsCache.Do(path, func() any {
+       return latestVersionIgnoringRetractionsCache.Do(path, func() (module.Version, error) {
                ctx, span := trace.StartSpan(ctx, "queryLatestVersionIgnoringRetractions "+path)
                defer span.Done()
 
                if repl := Replacement(module.Version{Path: path}); repl.Path != "" {
                        // All versions of the module were replaced.
                        // No need to query.
-                       return &entry{latest: repl}
+                       return repl, nil
                }
 
                // Find the latest version of the module.
@@ -785,18 +771,17 @@ func queryLatestVersionIgnoringRetractions(ctx context.Context, path string) (la
                var allowAll AllowedFunc
                rev, err := Query(ctx, path, "latest", ignoreSelected, allowAll)
                if err != nil {
-                       return &entry{err: err}
+                       return module.Version{}, err
                }
                latest := module.Version{Path: path, Version: rev.Version}
                if repl := resolveReplacement(latest); repl.Path != "" {
                        latest = repl
                }
-               return &entry{latest: latest}
-       }).(*entry)
-       return e.latest, e.err
+               return latest, nil
+       })
 }
 
-var latestVersionIgnoringRetractionsCache par.Cache // path → queryLatestVersionIgnoringRetractions result
+var latestVersionIgnoringRetractionsCache par.ErrCache[string, module.Version] // path → queryLatestVersionIgnoringRetractions result
 
 // ToDirectoryPath adds a prefix if necessary so that path in unambiguously
 // an absolute path or a relative path starting with a '.' or '..'
index 8912a3aa7dc423d162c9a84a16b1ddc8288e65c4..5b6de9425a583f17c67b8c2da7de5b2a3d17dd0d 100644 (file)
@@ -6,6 +6,7 @@
 package par
 
 import (
+       "errors"
        "math/rand"
        "sync"
        "sync/atomic"
@@ -102,26 +103,57 @@ func (w *Work[T]) runner() {
        }
 }
 
+// ErrCache is like Cache except that it also stores
+// an error value alongside the cached value V.
+type ErrCache[K comparable, V any] struct {
+       Cache[K, errValue[V]]
+}
+
+type errValue[V any] struct {
+       v   V
+       err error
+}
+
+func (c *ErrCache[K, V]) Do(key K, f func() (V, error)) (V, error) {
+       v := c.Cache.Do(key, func() errValue[V] {
+               v, err := f()
+               return errValue[V]{v, err}
+       })
+       return v.v, v.err
+}
+
+var ErrCacheEntryNotFound = errors.New("cache entry not found")
+
+// Get returns the cached result associated with key.
+// It returns ErrCacheEntryNotFound if there is no such result.
+func (c *ErrCache[K, V]) Get(key K) (V, error) {
+       v, ok := c.Cache.Get(key)
+       if !ok {
+               v.err = ErrCacheEntryNotFound
+       }
+       return v.v, v.err
+}
+
 // Cache runs an action once per key and caches the result.
-type Cache struct {
+type Cache[K comparable, V any] struct {
        m sync.Map
 }
 
-type cacheEntry struct {
+type cacheEntry[V any] struct {
        done   atomic.Bool
        mu     sync.Mutex
-       result any
+       result V
 }
 
 // Do calls the function f if and only if Do is being called for the first time with this key.
 // No call to Do with a given key returns until the one call to f returns.
 // Do returns the value returned by the one call to f.
-func (c *Cache) Do(key any, f func() any) any {
+func (c *Cache[K, V]) Do(key K, f func() V) V {
        entryIface, ok := c.m.Load(key)
        if !ok {
-               entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry))
+               entryIface, _ = c.m.LoadOrStore(key, new(cacheEntry[V]))
        }
-       e := entryIface.(*cacheEntry)
+       e := entryIface.(*cacheEntry[V])
        if !e.done.Load() {
                e.mu.Lock()
                if !e.done.Load() {
@@ -133,19 +165,20 @@ func (c *Cache) Do(key any, f func() any) any {
        return e.result
 }
 
-// Get returns the cached result associated with key.
-// It returns nil if there is no such result.
+// Get returns the cached result associated with key
+// and reports whether there is such a result.
+//
 // If the result for key is being computed, Get does not wait for the computation to finish.
-func (c *Cache) Get(key any) any {
+func (c *Cache[K, V]) Get(key K) (V, bool) {
        entryIface, ok := c.m.Load(key)
        if !ok {
-               return nil
+               return *new(V), false
        }
-       e := entryIface.(*cacheEntry)
+       e := entryIface.(*cacheEntry[V])
        if !e.done.Load() {
-               return nil
+               return *new(V), false
        }
-       return e.result
+       return e.result, true
 }
 
 // Clear removes all entries in the cache.
@@ -155,7 +188,7 @@ func (c *Cache) Get(key any) any {
 //
 // TODO(jayconrod): Delete this after the package cache clearing functions
 // in internal/load have been removed.
-func (c *Cache) Clear() {
+func (c *Cache[K, V]) Clear() {
        c.m.Range(func(key, value any) bool {
                c.m.Delete(key)
                return true
@@ -169,7 +202,7 @@ func (c *Cache) Clear() {
 //
 // TODO(jayconrod): Delete this after the package cache clearing functions
 // in internal/load have been removed.
-func (c *Cache) Delete(key any) {
+func (c *Cache[K, V]) Delete(key K) {
        c.m.Delete(key)
 }
 
@@ -180,9 +213,9 @@ func (c *Cache) Delete(key any) {
 //
 // TODO(jayconrod): Delete this after the package cache clearing functions
 // in internal/load have been removed.
-func (c *Cache) DeleteIf(pred func(key any) bool) {
+func (c *Cache[K, V]) DeleteIf(pred func(key K) bool) {
        c.m.Range(func(key, _ any) bool {
-               if pred(key) {
+               if key := key.(K); pred(key) {
                        c.Delete(key)
                }
                return true
index 4283e0d08a63f06378a1761ad526ae3df06155fe..9d96ffae50ca558db797bd0b4c2d230e767b3faa 100644 (file)
@@ -54,22 +54,22 @@ func TestWorkParallel(t *testing.T) {
 }
 
 func TestCache(t *testing.T) {
-       var cache Cache
+       var cache Cache[int, int]
 
        n := 1
-       v := cache.Do(1, func() any { n++; return n })
+       v := cache.Do(1, func() int { n++; return n })
        if v != 2 {
                t.Fatalf("cache.Do(1) did not run f")
        }
-       v = cache.Do(1, func() any { n++; return n })
+       v = cache.Do(1, func() int { n++; return n })
        if v != 2 {
                t.Fatalf("cache.Do(1) ran f again!")
        }
-       v = cache.Do(2, func() any { n++; return n })
+       v = cache.Do(2, func() int { n++; return n })
        if v != 3 {
                t.Fatalf("cache.Do(2) did not run f")
        }
-       v = cache.Do(1, func() any { n++; return n })
+       v = cache.Do(1, func() int { n++; return n })
        if v != 2 {
                t.Fatalf("cache.Do(1) did not returned saved value from original cache.Do(1)")
        }
index fc256968b74bd1faa0f41237765a830abc3cc688..cb3d9f92f19b88662e299c2505a378de1a438a40 100644 (file)
@@ -105,7 +105,7 @@ func readModList() {
        }
 }
 
-var zipCache par.Cache
+var zipCache par.ErrCache[*txtar.Archive, []byte]
 
 const (
        testSumDBName        = "localhost.localdev/sumdb"
@@ -353,11 +353,7 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
                }
 
        case "zip":
-               type cached struct {
-                       zip []byte
-                       err error
-               }
-               c := zipCache.Do(a, func() any {
+               zipBytes, err := zipCache.Do(a, func() ([]byte, error) {
                        var buf bytes.Buffer
                        z := zip.NewWriter(&buf)
                        for _, f := range a.Files {
@@ -372,26 +368,26 @@ func proxyHandler(w http.ResponseWriter, r *http.Request) {
                                }
                                zf, err := z.Create(zipName)
                                if err != nil {
-                                       return cached{nil, err}
+                                       return nil, err
                                }
                                if _, err := zf.Write(f.Data); err != nil {
-                                       return cached{nil, err}
+                                       return nil, err
                                }
                        }
                        if err := z.Close(); err != nil {
-                               return cached{nil, err}
+                               return nil, err
                        }
-                       return cached{buf.Bytes(), nil}
-               }).(cached)
+                       return buf.Bytes(), nil
+               })
 
-               if c.err != nil {
+               if err != nil {
                        if testing.Verbose() {
-                               fmt.Fprintf(os.Stderr, "go proxy: %v\n", c.err)
+                               fmt.Fprintf(os.Stderr, "go proxy: %v\n", err)
                        }
-                       http.Error(w, c.err.Error(), 500)
+                       http.Error(w, err.Error(), 500)
                        return
                }
-               w.Write(c.zip)
+               w.Write(zipBytes)
                return
 
        }
@@ -415,7 +411,7 @@ func findHash(m module.Version) string {
        return info.Short
 }
 
-var archiveCache par.Cache
+var archiveCache par.Cache[string, *txtar.Archive]
 
 var cmdGoDir, _ = os.Getwd()
 
@@ -431,7 +427,7 @@ func readArchive(path, vers string) (*txtar.Archive, error) {
 
        prefix := strings.ReplaceAll(enc, "/", "_")
        name := filepath.Join(cmdGoDir, "testdata/mod", prefix+"_"+encVers+".txt")
-       a := archiveCache.Do(name, func() any {
+       a := archiveCache.Do(name, func() *txtar.Archive {
                a, err := txtar.ParseFile(name)
                if err != nil {
                        if testing.Verbose() || !os.IsNotExist(err) {
@@ -440,7 +436,7 @@ func readArchive(path, vers string) (*txtar.Archive, error) {
                        a = nil
                }
                return a
-       }).(*txtar.Archive)
+       })
        if a == nil {
                return nil, fs.ErrNotExist
        }