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 }