github.com/sykesm/fabric@v1.1.0-preview.0.20200129034918-2aa12b1a0181/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/hyperledger/fabric/common/flogging"
    15  	"github.com/hyperledger/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  	itr := dbInst.db.NewIterator(&goleveldbutil.Range{}, dbInst.readOpts)
    83  	defer itr.Release()
    84  	hasItems := itr.Next()
    85  	return !hasItems,
    86  		errors.Wrapf(itr.Error(), "error while trying to see if the leveldb at path [%s] is empty", dbInst.conf.DBPath)
    87  }
    88  
    89  // Close closes the underlying db
    90  func (dbInst *DB) Close() {
    91  	dbInst.mutex.Lock()
    92  	defer dbInst.mutex.Unlock()
    93  	if dbInst.dbState == closed {
    94  		return
    95  	}
    96  	if err := dbInst.db.Close(); err != nil {
    97  		logger.Errorf("Error closing leveldb: %s", err)
    98  	}
    99  	dbInst.dbState = closed
   100  }
   101  
   102  // Get returns the value for the given key
   103  func (dbInst *DB) Get(key []byte) ([]byte, error) {
   104  	dbInst.mutex.RLock()
   105  	defer dbInst.mutex.RUnlock()
   106  	value, err := dbInst.db.Get(key, dbInst.readOpts)
   107  	if err == leveldb.ErrNotFound {
   108  		value = nil
   109  		err = nil
   110  	}
   111  	if err != nil {
   112  		logger.Errorf("Error retrieving leveldb key [%#v]: %s", key, err)
   113  		return nil, errors.Wrapf(err, "error retrieving leveldb key [%#v]", key)
   114  	}
   115  	return value, nil
   116  }
   117  
   118  // Put saves the key/value
   119  func (dbInst *DB) Put(key []byte, value []byte, sync bool) error {
   120  	dbInst.mutex.RLock()
   121  	defer dbInst.mutex.RUnlock()
   122  	wo := dbInst.writeOptsNoSync
   123  	if sync {
   124  		wo = dbInst.writeOptsSync
   125  	}
   126  	err := dbInst.db.Put(key, value, wo)
   127  	if err != nil {
   128  		logger.Errorf("Error writing leveldb key [%#v]", key)
   129  		return errors.Wrapf(err, "error writing leveldb key [%#v]", key)
   130  	}
   131  	return nil
   132  }
   133  
   134  // Delete deletes the given key
   135  func (dbInst *DB) Delete(key []byte, sync bool) error {
   136  	dbInst.mutex.RLock()
   137  	defer dbInst.mutex.RUnlock()
   138  	wo := dbInst.writeOptsNoSync
   139  	if sync {
   140  		wo = dbInst.writeOptsSync
   141  	}
   142  	err := dbInst.db.Delete(key, wo)
   143  	if err != nil {
   144  		logger.Errorf("Error deleting leveldb key [%#v]", key)
   145  		return errors.Wrapf(err, "error deleting leveldb key [%#v]", key)
   146  	}
   147  	return nil
   148  }
   149  
   150  // GetIterator returns an iterator over key-value store. The iterator should be released after the use.
   151  // The resultset contains all the keys that are present in the db between the startKey (inclusive) and the endKey (exclusive).
   152  // A nil startKey represents the first available key and a nil endKey represent a logical key after the last available key
   153  func (dbInst *DB) GetIterator(startKey []byte, endKey []byte) iterator.Iterator {
   154  	dbInst.mutex.RLock()
   155  	defer dbInst.mutex.RUnlock()
   156  	return dbInst.db.NewIterator(&goleveldbutil.Range{Start: startKey, Limit: endKey}, dbInst.readOpts)
   157  }
   158  
   159  // WriteBatch writes a batch
   160  func (dbInst *DB) WriteBatch(batch *leveldb.Batch, sync bool) error {
   161  	dbInst.mutex.RLock()
   162  	defer dbInst.mutex.RUnlock()
   163  	wo := dbInst.writeOptsNoSync
   164  	if sync {
   165  		wo = dbInst.writeOptsSync
   166  	}
   167  	if err := dbInst.db.Write(batch, wo); err != nil {
   168  		return errors.Wrap(err, "error writing batch to leveldb")
   169  	}
   170  	return nil
   171  }
   172  
   173  // FileLock encapsulate the DB that holds the file lock.
   174  // As the FileLock to be used by a single process/goroutine,
   175  // there is no need for the semaphore to synchronize the
   176  // FileLock usage.
   177  type FileLock struct {
   178  	db       *leveldb.DB
   179  	filePath string
   180  }
   181  
   182  // NewFileLock returns a new file based lock manager.
   183  func NewFileLock(filePath string) *FileLock {
   184  	return &FileLock{
   185  		filePath: filePath,
   186  	}
   187  }
   188  
   189  // Lock acquire a file lock. We achieve this by opening
   190  // a db for the given filePath. Internally, leveldb acquires a
   191  // file lock while opening a db. If the db is opened again by the same or
   192  // another process, error would be returned. When the db is closed
   193  // or the owner process dies, the lock would be released and hence
   194  // the other process can open the db. We exploit this leveldb
   195  // functionality to acquire and release file lock as the leveldb
   196  // supports this for Windows, Solaris, and Unix.
   197  func (f *FileLock) Lock() error {
   198  	dbOpts := &opt.Options{}
   199  	var err error
   200  	var dirEmpty bool
   201  	if dirEmpty, err = util.CreateDirIfMissing(f.filePath); err != nil {
   202  		panic(fmt.Sprintf("Error creating dir if missing: %s", err))
   203  	}
   204  	dbOpts.ErrorIfMissing = !dirEmpty
   205  	f.db, err = leveldb.OpenFile(f.filePath, dbOpts)
   206  	if err != nil && err == syscall.EAGAIN {
   207  		return errors.Errorf("lock is already acquired on file %s", f.filePath)
   208  	}
   209  	if err != nil {
   210  		panic(fmt.Sprintf("Error acquiring lock on file %s: %s", f.filePath, err))
   211  	}
   212  	return nil
   213  }
   214  
   215  // Unlock releases a previously acquired lock. We achieve this by closing
   216  // the previously opened db. FileUnlock can be called multiple times.
   217  func (f *FileLock) Unlock() {
   218  	if f.db == nil {
   219  		return
   220  	}
   221  	if err := f.db.Close(); err != nil {
   222  		logger.Warningf("unable to release the lock on file %s: %s", f.filePath, err)
   223  		return
   224  	}
   225  	f.db = nil
   226  }