package blobovniczatree import ( "fmt" "sync" utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync" "github.com/hashicorp/golang-lru/v2/simplelru" ) // dbCache caches sharedDB instances that are NOT open for Put. // // Uses dbManager for opening/closing sharedDB instances. // Stores a reference to an cached sharedDB, so dbManager does not close it. type dbCache struct { cacheGuard *sync.RWMutex cache simplelru.LRUCache[string, *sharedDB] pathLock *utilSync.KeyLocker[string] // the order of locks is important: pathLock first, cacheGuard second closed bool nonCached map[string]struct{} dbManager *dbManager } func newDBCache(size int, dbManager *dbManager) *dbCache { cache, err := simplelru.NewLRU(size, func(_ string, evictedDB *sharedDB) { evictedDB.Close() }) if err != nil { // occurs only if the size is not positive panic(fmt.Errorf("could not create LRU cache of size %d: %w", size, err)) } return &dbCache{ cacheGuard: &sync.RWMutex{}, cache: cache, dbManager: dbManager, pathLock: utilSync.NewKeyLocker[string](), nonCached: make(map[string]struct{}), } } func (c *dbCache) Open() { c.cacheGuard.Lock() defer c.cacheGuard.Unlock() c.closed = false } func (c *dbCache) Close() { c.cacheGuard.Lock() defer c.cacheGuard.Unlock() c.cache.Purge() c.closed = true } func (c *dbCache) GetOrCreate(path string) *sharedDB { value := c.getExisted(path) if value != nil { return value } return c.create(path) } func (c *dbCache) EvictAndMarkNonCached(path string) { c.pathLock.Lock(path) defer c.pathLock.Unlock(path) c.cacheGuard.Lock() defer c.cacheGuard.Unlock() c.cache.Remove(path) c.nonCached[path] = struct{}{} } func (c *dbCache) RemoveFromNonCached(path string) { c.pathLock.Lock(path) defer c.pathLock.Unlock(path) c.cacheGuard.Lock() defer c.cacheGuard.Unlock() delete(c.nonCached, path) } func (c *dbCache) getExisted(path string) *sharedDB { c.cacheGuard.Lock() defer c.cacheGuard.Unlock() if value, ok := c.cache.Get(path); ok { return value } return nil } func (c *dbCache) create(path string) *sharedDB { c.pathLock.Lock(path) defer c.pathLock.Unlock(path) value := c.getExisted(path) if value != nil { return value } value = c.dbManager.GetByPath(path) _, err := value.Open() // open db to hold reference, closed by evictedDB.Close() or if cache closed if err != nil { return value } if added := c.put(path, value); !added { value.Close() } return value } func (c *dbCache) put(path string, db *sharedDB) bool { c.cacheGuard.Lock() defer c.cacheGuard.Unlock() _, isNonCached := c.nonCached[path] if !isNonCached && !c.closed { c.cache.Add(path, db) return true } return false }