github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/common/ledger/util/leveldbhelper/leveldb_helper.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package leveldbhelper
     8  
     9  import (
    10  	"fmt"
    11  	"sync"
    12  	"syscall"
    13  
    14  	"github.com/osdi23p228/fabric/common/flogging"
    15  	"github.com/osdi23p228/fabric/common/ledger/util"
    16  	"github.com/pkg/errors"
    17  	"github.com/syndtr/goleveldb/leveldb"
    18  	"github.com/syndtr/goleveldb/leveldb/iterator"
    19  	"github.com/syndtr/goleveldb/leveldb/opt"
    20  	goleveldbutil "github.com/syndtr/goleveldb/leveldb/util"
    21  )
    22  
    23  var logger = flogging.MustGetLogger("leveldbhelper")
    24  
    25  type dbState int32
    26  
    27  const (
    28  	closed dbState = iota
    29  	opened
    30  )
    31  
    32  // DB - a wrapper on an actual store
    33  type DB struct {
    34  	conf    *Conf
    35  	db      *leveldb.DB
    36  	dbState dbState
    37  	mutex   sync.RWMutex
    38  
    39  	readOpts        *opt.ReadOptions
    40  	writeOptsNoSync *opt.WriteOptions
    41  	writeOptsSync   *opt.WriteOptions
    42  }
    43  
    44  // CreateDB constructs a `DB`
    45  func CreateDB(conf *Conf) *DB {
    46  	readOpts := &opt.ReadOptions{}
    47  	writeOptsNoSync := &opt.WriteOptions{}
    48  	writeOptsSync := &opt.WriteOptions{}
    49  	writeOptsSync.Sync = true
    50  
    51  	return &DB{
    52  		conf:            conf,
    53  		dbState:         closed,
    54  		readOpts:        readOpts,
    55  		writeOptsNoSync: writeOptsNoSync,
    56  		writeOptsSync:   writeOptsSync}
    57  }
    58  
    59  // Open opens the underlying db
    60  func (dbInst *DB) Open() {
    61  	dbInst.mutex.Lock()
    62  	defer dbInst.mutex.Unlock()
    63  	if dbInst.dbState == opened {
    64  		return
    65  	}
    66  	dbOpts := &opt.Options{}
    67  	dbPath := dbInst.conf.DBPath
    68  	var err error
    69  	var dirEmpty bool
    70  	if dirEmpty, err = util.CreateDirIfMissing(dbPath); err != nil {
    71  		panic(fmt.Sprintf("Error creating dir if missing: %s", err))
    72  	}
    73  	dbOpts.ErrorIfMissing = !dirEmpty
    74  	if dbInst.db, err = leveldb.OpenFile(dbPath, dbOpts); err != nil {
    75  		panic(fmt.Sprintf("Error opening leveldb: %s", err))
    76  	}
    77  	dbInst.dbState = opened
    78  }
    79  
    80  // IsEmpty returns whether or not a database is empty
    81  func (dbInst *DB) IsEmpty() (bool, error) {
    82  	dbInst.mutex.RLock()
    83  	defer dbInst.mutex.RUnlock()
    84  	itr := dbInst.db.NewIterator(&goleveldbutil.Range{}, dbInst.readOpts)
    85  	defer itr.Release()
    86  	hasItems := itr.Next()
    87  	return !hasItems,
    88  		errors.Wrapf(itr.Error(), "error while trying to see if the leveldb at path [%s] is empty", dbInst.conf.DBPath)
    89  }
    90  
    91  // Close closes the underlying db
    92  func (dbInst *DB) Close() {
    93  	dbInst.mutex.Lock()
    94  	defer dbInst.mutex.Unlock()
    95  	if dbInst.dbState == closed {
    96  		return
    97  	}
    98  	if err := dbInst.db.Close(); err != nil {
    99  		logger.Errorf("Error closing leveldb: %s", err)
   100  	}
   101  	dbInst.dbState = closed
   102  }
   103  
   104  // Get returns the value for the given key
   105  func (dbInst *DB) Get(key []byte) ([]byte, error) {
   106  	dbInst.mutex.RLock()
   107  	defer dbInst.mutex.RUnlock()
   108  	value, err := dbInst.db.Get(key, dbInst.readOpts)
   109  	if err == leveldb.ErrNotFound {
   110  		value = nil
   111  		err = nil
   112  	}
   113  	if err != nil {
   114  		logger.Errorf("Error retrieving leveldb key [%#v]: %s", key, err)
   115  		return nil, errors.Wrapf(err, "error retrieving leveldb key [%#v]", key)
   116  	}
   117  	return value, nil
   118  }
   119  
   120  // Put saves the key/value
   121  func (dbInst *DB) Put(key []byte, value []byte, sync bool) error {
   122  	dbInst.mutex.RLock()
   123  	defer dbInst.mutex.RUnlock()
   124  	wo := dbInst.writeOptsNoSync
   125  	if sync {
   126  		wo = dbInst.writeOptsSync
   127  	}
   128  	err := dbInst.db.Put(key, value, wo)
   129  	if err != nil {
   130  		logger.Errorf("Error writing leveldb key [%#v]", key)
   131  		return errors.Wrapf(err, "error writing leveldb key [%#v]", key)
   132  	}
   133  	return nil
   134  }
   135  
   136  // Delete deletes the given key
   137  func (dbInst *DB) Delete(key []byte, sync bool) error {
   138  	dbInst.mutex.RLock()
   139  	defer dbInst.mutex.RUnlock()
   140  	wo := dbInst.writeOptsNoSync
   141  	if sync {
   142  		wo = dbInst.writeOptsSync
   143  	}
   144  	err := dbInst.db.Delete(key, wo)
   145  	if err != nil {
   146  		logger.Errorf("Error deleting leveldb key [%#v]", key)
   147  		return errors.Wrapf(err, "error deleting leveldb key [%#v]", key)
   148  	}
   149  	return nil
   150  }
   151  
   152  // GetIterator returns an iterator over key-value store. The iterator should be released after the use.
   153  // The resultset contains all the keys that are present in the db between the startKey (inclusive) and the endKey (exclusive).
   154  // A nil startKey represents the first available key and a nil endKey represent a logical key after the last available key
   155  func (dbInst *DB) GetIterator(startKey []byte, endKey []byte) iterator.Iterator {
   156  	dbInst.mutex.RLock()
   157  	defer dbInst.mutex.RUnlock()
   158  	return dbInst.db.NewIterator(&goleveldbutil.Range{Start: startKey, Limit: endKey}, dbInst.readOpts)
   159  }
   160  
   161  // WriteBatch writes a batch
   162  func (dbInst *DB) WriteBatch(batch *leveldb.Batch, sync bool) error {
   163  	dbInst.mutex.RLock()
   164  	defer dbInst.mutex.RUnlock()
   165  	wo := dbInst.writeOptsNoSync
   166  	if sync {
   167  		wo = dbInst.writeOptsSync
   168  	}
   169  	if err := dbInst.db.Write(batch, wo); err != nil {
   170  		return errors.Wrap(err, "error writing batch to leveldb")
   171  	}
   172  	return nil
   173  }
   174  
   175  // FileLock encapsulate the DB that holds the file lock.
   176  // As the FileLock to be used by a single process/goroutine,
   177  // there is no need for the semaphore to synchronize the
   178  // FileLock usage.
   179  type FileLock struct {
   180  	db       *leveldb.DB
   181  	filePath string
   182  }
   183  
   184  // NewFileLock returns a new file based lock manager.
   185  func NewFileLock(filePath string) *FileLock {
   186  	return &FileLock{
   187  		filePath: filePath,
   188  	}
   189  }
   190  
   191  // Lock acquire a file lock. We achieve this by opening
   192  // a db for the given filePath. Internally, leveldb acquires a
   193  // file lock while opening a db. If the db is opened again by the same or
   194  // another process, error would be returned. When the db is closed
   195  // or the owner process dies, the lock would be released and hence
   196  // the other process can open the db. We exploit this leveldb
   197  // functionality to acquire and release file lock as the leveldb
   198  // supports this for Windows, Solaris, and Unix.
   199  func (f *FileLock) Lock() error {
   200  	dbOpts := &opt.Options{}
   201  	var err error
   202  	var dirEmpty bool
   203  	if dirEmpty, err = util.CreateDirIfMissing(f.filePath); err != nil {
   204  		panic(fmt.Sprintf("Error creating dir if missing: %s", err))
   205  	}
   206  	dbOpts.ErrorIfMissing = !dirEmpty
   207  	f.db, err = leveldb.OpenFile(f.filePath, dbOpts)
   208  	if err != nil && err == syscall.EAGAIN {
   209  		return errors.Errorf("lock is already acquired on file %s", f.filePath)
   210  	}
   211  	if err != nil {
   212  		panic(fmt.Sprintf("Error acquiring lock on file %s: %s", f.filePath, err))
   213  	}
   214  	return nil
   215  }
   216  
   217  // Unlock releases a previously acquired lock. We achieve this by closing
   218  // the previously opened db. FileUnlock can be called multiple times.
   219  func (f *FileLock) Unlock() {
   220  	if f.db == nil {
   221  		return
   222  	}
   223  	if err := f.db.Close(); err != nil {
   224  		logger.Warningf("unable to release the lock on file %s: %s", f.filePath, err)
   225  		return
   226  	}
   227  	f.db = nil
   228  }