github.com/decred/dcrlnd@v0.7.6/kvdb/etcd/readwrite_tx.go (about) 1 //go:build kvdb_etcd 2 // +build kvdb_etcd 3 4 package etcd 5 6 import ( 7 "sync" 8 9 "github.com/btcsuite/btcwallet/walletdb" 10 ) 11 12 // readWriteTx holds a reference to the STM transaction. 13 type readWriteTx struct { 14 // stm is the reference to the parent STM. 15 stm STM 16 17 // rootBucketID holds the sha256 hash of the root bucket id, which is used 18 // for key space spearation. 19 rootBucketID [bucketIDLength]byte 20 21 // active is true if the transaction hasn't been committed yet. 22 active bool 23 24 // lock is passed on for manual txns when the backend is instantiated 25 // such that we read/write lock transactions to ensure a single writer. 26 lock sync.Locker 27 } 28 29 // newReadWriteTx creates an rw transaction with the passed STM. 30 func newReadWriteTx(stm STM, prefix string, lock sync.Locker) *readWriteTx { 31 return &readWriteTx{ 32 stm: stm, 33 active: true, 34 lock: lock, 35 rootBucketID: makeBucketID([]byte(prefix)), 36 } 37 } 38 39 // rooBucket is a helper function to return the always present 40 // pseudo root bucket. 41 func rootBucket(tx *readWriteTx) *readWriteBucket { 42 return newReadWriteBucket(tx, tx.rootBucketID[:], tx.rootBucketID[:]) 43 } 44 45 // RootBucket will return a handle to the root bucket. This is not a real handle 46 // but just a wrapper around the root bucket ID to allow derivation of child 47 // keys. 48 func (tx *readWriteTx) RootBucket() walletdb.ReadBucket { 49 return rootBucket(tx) 50 } 51 52 // ReadBucket opens the root bucket for read only access. If the bucket 53 // described by the key does not exist, nil is returned. 54 func (tx *readWriteTx) ReadBucket(key []byte) walletdb.ReadBucket { 55 return rootBucket(tx).NestedReadWriteBucket(key) 56 } 57 58 // ForEachBucket iterates through all top level buckets. 59 func (tx *readWriteTx) ForEachBucket(fn func(key []byte) error) error { 60 root := rootBucket(tx) 61 // We can safely use ForEach here since on the top level there are 62 // no values, only buckets. 63 return root.ForEach(func(key []byte, val []byte) error { 64 if val != nil { 65 // A non-nil value would mean that we have a non 66 // walletdb/kvdb compatibel database containing 67 // arbitrary key/values. 68 return walletdb.ErrInvalid 69 70 } 71 72 return fn(key) 73 }) 74 } 75 76 // Rollback closes the transaction, discarding changes (if any) if the 77 // database was modified by a write transaction. 78 func (tx *readWriteTx) Rollback() error { 79 // If the transaction has been closed roolback will fail. 80 if !tx.active { 81 return walletdb.ErrTxClosed 82 } 83 84 if tx.lock != nil { 85 defer tx.lock.Unlock() 86 } 87 88 // Rollback the STM and set the tx to inactive. 89 tx.stm.Rollback() 90 tx.active = false 91 92 return nil 93 } 94 95 // ReadWriteBucket opens the root bucket for read/write access. If the 96 // bucket described by the key does not exist, nil is returned. 97 func (tx *readWriteTx) ReadWriteBucket(key []byte) walletdb.ReadWriteBucket { 98 return rootBucket(tx).NestedReadWriteBucket(key) 99 } 100 101 // CreateTopLevelBucket creates the top level bucket for a key if it 102 // does not exist. The newly-created bucket it returned. 103 func (tx *readWriteTx) CreateTopLevelBucket(key []byte) (walletdb.ReadWriteBucket, error) { 104 return rootBucket(tx).CreateBucketIfNotExists(key) 105 } 106 107 // DeleteTopLevelBucket deletes the top level bucket for a key. This 108 // errors if the bucket can not be found or the key keys a single value 109 // instead of a bucket. 110 func (tx *readWriteTx) DeleteTopLevelBucket(key []byte) error { 111 return rootBucket(tx).DeleteNestedBucket(key) 112 } 113 114 // Commit commits the transaction if not already committed. Will return 115 // error if the underlying STM fails. 116 func (tx *readWriteTx) Commit() error { 117 // Commit will fail if the transaction is already committed. 118 if !tx.active { 119 return walletdb.ErrTxClosed 120 } 121 122 if tx.lock != nil { 123 defer tx.lock.Unlock() 124 } 125 126 // Try committing the transaction. 127 if err := tx.stm.Commit(); err != nil { 128 return err 129 } 130 131 // Mark the transaction as not active after commit. 132 tx.active = false 133 134 return nil 135 } 136 137 // OnCommit sets the commit callback (overriding if already set). 138 func (tx *readWriteTx) OnCommit(cb func()) { 139 tx.stm.OnCommit(cb) 140 }