forked from TrueCloudLab/frostfs-node
242 lines
5.7 KiB
Go
242 lines
5.7 KiB
Go
package blobovniczatree
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// sharedDB is responsible for opening and closing a file of single blobovnicza.
|
|
type sharedDB struct {
|
|
guard *sync.RWMutex
|
|
blcza *blobovnicza.Blobovnicza
|
|
refCount uint32
|
|
|
|
openDBCounter *openDBCounter
|
|
closedFlag *atomic.Bool
|
|
options []blobovnicza.Option
|
|
path string
|
|
readOnly bool
|
|
metrics blobovnicza.Metrics
|
|
log *logger.Logger
|
|
}
|
|
|
|
func newSharedDB(options []blobovnicza.Option, path string, readOnly bool,
|
|
metrics blobovnicza.Metrics, openDBCounter *openDBCounter, closedFlag *atomic.Bool, log *logger.Logger) *sharedDB {
|
|
return &sharedDB{
|
|
guard: &sync.RWMutex{},
|
|
|
|
options: options,
|
|
path: path,
|
|
readOnly: readOnly,
|
|
metrics: metrics,
|
|
closedFlag: closedFlag,
|
|
log: log,
|
|
openDBCounter: openDBCounter,
|
|
}
|
|
}
|
|
|
|
func (b *sharedDB) Open() (*blobovnicza.Blobovnicza, error) {
|
|
if b.closedFlag.Load() {
|
|
return nil, errClosed
|
|
}
|
|
|
|
b.guard.Lock()
|
|
defer b.guard.Unlock()
|
|
|
|
if b.refCount > 0 {
|
|
b.refCount++
|
|
return b.blcza, nil
|
|
}
|
|
|
|
blz := blobovnicza.New(append(b.options,
|
|
blobovnicza.WithReadOnly(b.readOnly),
|
|
blobovnicza.WithPath(b.path),
|
|
blobovnicza.WithMetrics(b.metrics),
|
|
)...)
|
|
|
|
if err := blz.Open(); err != nil {
|
|
return nil, fmt.Errorf("could not open blobovnicza %s: %w", b.path, err)
|
|
}
|
|
if err := blz.Init(); err != nil {
|
|
return nil, fmt.Errorf("could not init blobovnicza %s: %w", b.path, err)
|
|
}
|
|
|
|
b.refCount++
|
|
b.blcza = blz
|
|
b.openDBCounter.Inc()
|
|
|
|
return blz, nil
|
|
}
|
|
|
|
func (b *sharedDB) Close() {
|
|
b.guard.Lock()
|
|
defer b.guard.Unlock()
|
|
|
|
if b.refCount == 0 {
|
|
b.log.Error(logs.AttemtToCloseAlreadyClosedBlobovnicza, zap.String("id", b.path))
|
|
return
|
|
}
|
|
|
|
if b.refCount == 1 {
|
|
b.refCount = 0
|
|
if err := b.blcza.Close(); err != nil {
|
|
b.log.Error(logs.BlobovniczatreeCouldNotCloseBlobovnicza,
|
|
zap.String("id", b.path),
|
|
zap.String("error", err.Error()),
|
|
)
|
|
}
|
|
b.blcza = nil
|
|
b.openDBCounter.Dec()
|
|
return
|
|
}
|
|
|
|
b.refCount--
|
|
}
|
|
|
|
func (b *sharedDB) Path() string {
|
|
return b.path
|
|
}
|
|
|
|
// levelDbManager stores pointers of the sharedDB's for the leaf directory of the blobovnicza tree.
|
|
type levelDbManager struct {
|
|
databases []*sharedDB
|
|
}
|
|
|
|
func newLevelDBManager(width uint64, options []blobovnicza.Option, rootPath string, lvlPath string,
|
|
readOnly bool, metrics blobovnicza.Metrics, openDBCounter *openDBCounter, closedFlog *atomic.Bool, log *logger.Logger) *levelDbManager {
|
|
result := &levelDbManager{
|
|
databases: make([]*sharedDB, width),
|
|
}
|
|
for idx := uint64(0); idx < width; idx++ {
|
|
result.databases[idx] = newSharedDB(options, filepath.Join(rootPath, lvlPath, u64ToHexString(idx)), readOnly, metrics, openDBCounter, closedFlog, log)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (m *levelDbManager) GetByIndex(idx uint64) *sharedDB {
|
|
return m.databases[idx]
|
|
}
|
|
|
|
// dbManager manages the opening and closing of blobovnicza instances.
|
|
//
|
|
// The blobovnicza opens at the first request, closes after the last request.
|
|
type dbManager struct {
|
|
levelToManager map[string]*levelDbManager
|
|
levelToManagerGuard *sync.RWMutex
|
|
closedFlag *atomic.Bool
|
|
dbCounter *openDBCounter
|
|
|
|
rootPath string
|
|
options []blobovnicza.Option
|
|
readOnly bool
|
|
metrics blobovnicza.Metrics
|
|
leafWidth uint64
|
|
log *logger.Logger
|
|
}
|
|
|
|
func newDBManager(rootPath string, options []blobovnicza.Option, leafWidth uint64, readOnly bool, metrics blobovnicza.Metrics, log *logger.Logger) *dbManager {
|
|
return &dbManager{
|
|
rootPath: rootPath,
|
|
options: options,
|
|
readOnly: readOnly,
|
|
metrics: metrics,
|
|
leafWidth: leafWidth,
|
|
levelToManager: make(map[string]*levelDbManager),
|
|
levelToManagerGuard: &sync.RWMutex{},
|
|
log: log,
|
|
closedFlag: &atomic.Bool{},
|
|
dbCounter: newOpenDBCounter(),
|
|
}
|
|
}
|
|
|
|
func (m *dbManager) GetByPath(path string) *sharedDB {
|
|
lvlPath := filepath.Dir(path)
|
|
curIndex := u64FromHexString(filepath.Base(path))
|
|
levelManager := m.getLevelManager(lvlPath)
|
|
return levelManager.GetByIndex(curIndex)
|
|
}
|
|
|
|
func (m *dbManager) Open() {
|
|
m.closedFlag.Store(false)
|
|
}
|
|
|
|
func (m *dbManager) Close() {
|
|
m.closedFlag.Store(true)
|
|
m.dbCounter.WaitUntilAllClosed()
|
|
}
|
|
|
|
func (m *dbManager) getLevelManager(lvlPath string) *levelDbManager {
|
|
result := m.getLevelManagerIfExists(lvlPath)
|
|
if result != nil {
|
|
return result
|
|
}
|
|
return m.getOrCreateLevelManager(lvlPath)
|
|
}
|
|
|
|
func (m *dbManager) getLevelManagerIfExists(lvlPath string) *levelDbManager {
|
|
m.levelToManagerGuard.RLock()
|
|
defer m.levelToManagerGuard.RUnlock()
|
|
|
|
return m.levelToManager[lvlPath]
|
|
}
|
|
|
|
func (m *dbManager) getOrCreateLevelManager(lvlPath string) *levelDbManager {
|
|
m.levelToManagerGuard.Lock()
|
|
defer m.levelToManagerGuard.Unlock()
|
|
|
|
if result, ok := m.levelToManager[lvlPath]; ok {
|
|
return result
|
|
}
|
|
|
|
result := newLevelDBManager(m.leafWidth, m.options, m.rootPath, lvlPath, m.readOnly, m.metrics, m.dbCounter, m.closedFlag, m.log)
|
|
m.levelToManager[lvlPath] = result
|
|
return result
|
|
}
|
|
|
|
type openDBCounter struct {
|
|
cond *sync.Cond
|
|
count uint64
|
|
}
|
|
|
|
func newOpenDBCounter() *openDBCounter {
|
|
return &openDBCounter{
|
|
cond: &sync.Cond{
|
|
L: &sync.Mutex{},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (c *openDBCounter) Inc() {
|
|
c.cond.L.Lock()
|
|
defer c.cond.L.Unlock()
|
|
|
|
c.count++
|
|
}
|
|
|
|
func (c *openDBCounter) Dec() {
|
|
c.cond.L.Lock()
|
|
defer c.cond.L.Unlock()
|
|
|
|
if c.count > 0 {
|
|
c.count--
|
|
}
|
|
|
|
if c.count == 0 {
|
|
c.cond.Broadcast()
|
|
}
|
|
}
|
|
|
|
func (c *openDBCounter) WaitUntilAllClosed() {
|
|
c.cond.L.Lock()
|
|
for c.count > 0 {
|
|
c.cond.Wait()
|
|
}
|
|
c.cond.L.Unlock()
|
|
}
|