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 }