github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/common/ledger/util/leveldbhelper/leveldb_helper.go (about)

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