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 }