diff --git a/internal/resolver/resolver.go b/internal/resolver/resolver.go index 70ce6a55b..018f14e36 100644 --- a/internal/resolver/resolver.go +++ b/internal/resolver/resolver.go @@ -123,7 +123,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string var ok bool found := true if !registry.IsOCI(d.Repository) { - repoIndex, err := repo.LoadIndexFile(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) + repoIndex, err := repo.LoadIndexFileWithCaching(filepath.Join(r.cachepath, helmpath.CacheIndexFile(repoName))) if err != nil { return nil, errors.Wrapf(err, "no cached repository for %s found. (try 'helm repo update')", repoName) } diff --git a/pkg/downloader/chart_downloader.go b/pkg/downloader/chart_downloader.go index 93afb1461..e3cb8e572 100644 --- a/pkg/downloader/chart_downloader.go +++ b/pkg/downloader/chart_downloader.go @@ -356,7 +356,7 @@ func (c *ChartDownloader) scanReposForURL(u string, rf *repo.File) (*repo.Entry, } idxFile := filepath.Join(c.RepositoryCache, helmpath.CacheIndexFile(r.Config.Name)) - i, err := repo.LoadIndexFile(idxFile) + i, err := repo.LoadIndexFileWithCaching(idxFile) if err != nil { return nil, errors.Wrap(err, "no cached repo found. (try 'helm repo update')") } diff --git a/pkg/downloader/manager.go b/pkg/downloader/manager.go index 52f1a1312..5c78a84a2 100644 --- a/pkg/downloader/manager.go +++ b/pkg/downloader/manager.go @@ -824,7 +824,7 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err for _, re := range rf.Repositories { lname := re.Name idxFile := filepath.Join(m.RepositoryCache, helmpath.CacheIndexFile(lname)) - index, err := repo.LoadIndexFile(idxFile) + index, err := repo.LoadIndexFileWithCaching(idxFile) if err != nil { return indices, err } diff --git a/pkg/repo/index.go b/pkg/repo/index.go index 1b65ac497..34609bb2b 100644 --- a/pkg/repo/index.go +++ b/pkg/repo/index.go @@ -25,6 +25,7 @@ import ( "path/filepath" "sort" "strings" + "sync" "time" "github.com/Masterminds/semver/v3" @@ -115,6 +116,30 @@ func LoadIndexFile(path string) (*IndexFile, error) { return i, nil } +var cache = make(map[string]*IndexFile) +var cacheLock sync.RWMutex + +func LoadIndexFileWithCaching(path string) (*IndexFile, error) { + // if already in cache, return cached entry + cacheLock.RLock() + if idx, ok := cache[path]; ok { + cacheLock.RUnlock() + // safe to return a pointer to the cached entry here since once in cache + // entry will never be overwritten + return idx, nil + } + cacheLock.RUnlock() + // not in cache, need to load from disk + idx, err := LoadIndexFile(path) + if err == nil { + // we fetched the index successfully. Store it in cache. + cacheLock.Lock() + cache[path] = idx + cacheLock.Unlock() + } + return idx, err +} + // MustAdd adds a file to the index // This can leave the index in an unsorted state func (i IndexFile) MustAdd(md *chart.Metadata, filename, baseURL, digest string) error {