blob: a259f027336a2c349a3f647df9bc8e040034224d [file] [log] [blame]
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.18
// +build go1.18
package govulncheck
import (
"sync"
"time"
vulnc "golang.org/x/vuln/client"
"golang.org/x/vuln/osv"
)
// inMemoryCache is an implementation of the [client.Cache] interface
// that "decorates" another instance of that interface to provide
// an additional layer of (memory-based) caching.
type inMemoryCache struct {
mu sync.Mutex
underlying vulnc.Cache
db map[string]*db
}
var _ vulnc.Cache = &inMemoryCache{}
type db struct {
retrieved time.Time
index vulnc.DBIndex
entry map[string][]*osv.Entry
}
// NewInMemoryCache returns a new memory-based cache that decorates
// the provided cache (file-based, perhaps).
func NewInMemoryCache(underlying vulnc.Cache) *inMemoryCache {
return &inMemoryCache{
underlying: underlying,
db: make(map[string]*db),
}
}
func (c *inMemoryCache) lookupDBLocked(dbName string) *db {
cached := c.db[dbName]
if cached == nil {
cached = &db{entry: make(map[string][]*osv.Entry)}
c.db[dbName] = cached
}
return cached
}
// ReadIndex returns the index for dbName from the cache, or returns zero values
// if it is not present.
func (c *inMemoryCache) ReadIndex(dbName string) (vulnc.DBIndex, time.Time, error) {
c.mu.Lock()
defer c.mu.Unlock()
cached := c.lookupDBLocked(dbName)
if cached.retrieved.IsZero() {
// First time ReadIndex is called.
index, retrieved, err := c.underlying.ReadIndex(dbName)
if err != nil {
return index, retrieved, err
}
cached.index, cached.retrieved = index, retrieved
}
return cached.index, cached.retrieved, nil
}
// WriteIndex puts the index and retrieved time into the cache.
func (c *inMemoryCache) WriteIndex(dbName string, index vulnc.DBIndex, retrieved time.Time) error {
c.mu.Lock()
defer c.mu.Unlock()
cached := c.lookupDBLocked(dbName)
cached.index, cached.retrieved = index, retrieved
// TODO(hyangah): shouldn't we invalidate all cached entries?
return c.underlying.WriteIndex(dbName, index, retrieved)
}
// ReadEntries returns the vulndb entries for path from the cache.
func (c *inMemoryCache) ReadEntries(dbName, path string) ([]*osv.Entry, error) {
c.mu.Lock()
defer c.mu.Unlock()
cached := c.lookupDBLocked(dbName)
entries, ok := cached.entry[path]
if !ok {
// cache miss
entries, err := c.underlying.ReadEntries(dbName, path)
if err != nil {
return entries, err
}
cached.entry[path] = entries
}
return entries, nil
}
// WriteEntries puts the entries for path into the cache.
func (c *inMemoryCache) WriteEntries(dbName, path string, entries []*osv.Entry) error {
c.mu.Lock()
defer c.mu.Unlock()
cached := c.lookupDBLocked(dbName)
cached.entry[path] = entries
return c.underlying.WriteEntries(dbName, path, entries)
}