157 lines
3.3 KiB
Go
157 lines
3.3 KiB
Go
package blobovniczatree
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync"
|
|
cache "github.com/go-pkgz/expirable-cache/v3"
|
|
)
|
|
|
|
// dbCache caches sharedDB instances that are NOT open for Put.
|
|
//
|
|
// Uses dbManager for opening/closing sharedDB instances.
|
|
// Stores a reference to a cached sharedDB, so dbManager does not close it.
|
|
type dbCache struct {
|
|
cacheGuard *sync.Mutex
|
|
cache cache.Cache[string, *sharedDB]
|
|
pathLock *utilSync.KeyLocker[string] // the order of locks is important: pathLock first, cacheGuard second
|
|
closed bool
|
|
nonCached map[string]struct{}
|
|
wg sync.WaitGroup
|
|
cancel context.CancelFunc
|
|
|
|
dbManager *dbManager
|
|
}
|
|
|
|
func newDBCache(parentCtx context.Context, size int,
|
|
ttl time.Duration, expInterval time.Duration,
|
|
dbManager *dbManager,
|
|
) *dbCache {
|
|
ch := cache.NewCache[string, *sharedDB]().
|
|
WithTTL(ttl).WithLRU().WithMaxKeys(size).
|
|
WithOnEvicted(func(_ string, db *sharedDB) {
|
|
db.Close()
|
|
})
|
|
ctx, cancel := context.WithCancel(parentCtx)
|
|
res := &dbCache{
|
|
cacheGuard: &sync.Mutex{},
|
|
wg: sync.WaitGroup{},
|
|
cancel: cancel,
|
|
cache: ch,
|
|
dbManager: dbManager,
|
|
pathLock: utilSync.NewKeyLocker[string](),
|
|
nonCached: make(map[string]struct{}),
|
|
}
|
|
if ttl > 0 {
|
|
res.wg.Add(1)
|
|
go func() {
|
|
ticker := time.NewTicker(expInterval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
res.wg.Done()
|
|
return
|
|
case <-ticker.C:
|
|
res.cacheGuard.Lock()
|
|
res.cache.DeleteExpired()
|
|
res.cacheGuard.Unlock()
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
return res
|
|
}
|
|
|
|
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.cancel()
|
|
c.wg.Wait()
|
|
c.cache.Purge()
|
|
c.closed = true
|
|
}
|
|
|
|
func (c *dbCache) GetOrCreate(ctx context.Context, path string) *sharedDB {
|
|
value := c.getExisted(path)
|
|
if value != nil {
|
|
return value
|
|
}
|
|
return c.create(ctx, 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
|
|
} else if value != nil {
|
|
c.cache.Invalidate(path)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *dbCache) create(ctx context.Context, 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(ctx) // 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 {
|
|
return false
|
|
}
|
|
c.cache.Add(path, db)
|
|
return true
|
|
}
|