github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/execution/proposal/cache.go (about) 1 // Copyright Monax Industries Limited 2 // SPDX-License-Identifier: Apache-2.0 3 4 package proposal 5 6 import ( 7 "bytes" 8 "crypto/sha256" 9 "fmt" 10 "sort" 11 "sync" 12 13 "github.com/hyperledger/burrow/txs/payload" 14 ) 15 16 // Cache helps prevent unnecessary IAVLTree updates and garbage generation. 17 type Cache struct { 18 sync.RWMutex 19 backend Reader 20 proposals map[[sha256.Size]byte]*proposalInfo 21 } 22 23 type proposalInfo struct { 24 sync.RWMutex 25 ballot *payload.Ballot 26 removed bool 27 updated bool 28 } 29 30 type ProposalHash [sha256.Size]byte 31 32 type ProposalHashArray []ProposalHash 33 34 func (p ProposalHashArray) Len() int { 35 return len(p) 36 } 37 38 func (p ProposalHashArray) Swap(i, j int) { 39 p[j], p[i] = p[i], p[j] 40 } 41 42 func (p ProposalHashArray) Less(i, j int) bool { 43 switch bytes.Compare(p[i][:], p[j][:]) { 44 case -1: 45 return true 46 case 0, 1: 47 return false 48 default: 49 panic("bytes.Compare returned invalid value") 50 } 51 } 52 53 var _ Writer = &Cache{} 54 55 // Returns a Cache, can write to an output Writer via Sync. 56 // Not goroutine safe, use syncStateCache if you need concurrent access 57 func NewCache(backend Reader) *Cache { 58 return &Cache{ 59 backend: backend, 60 proposals: make(map[[sha256.Size]byte]*proposalInfo), 61 } 62 } 63 64 func (cache *Cache) GetProposal(proposalHash []byte) (*payload.Ballot, error) { 65 proposalInfo, err := cache.get(proposalHash) 66 if err != nil { 67 return nil, err 68 } 69 proposalInfo.RLock() 70 defer proposalInfo.RUnlock() 71 if proposalInfo.removed { 72 return nil, nil 73 } 74 return proposalInfo.ballot, nil 75 } 76 77 func (cache *Cache) UpdateProposal(proposalHash []byte, ballot *payload.Ballot) error { 78 proposalInfo, err := cache.get(proposalHash) 79 if err != nil { 80 return err 81 } 82 proposalInfo.Lock() 83 defer proposalInfo.Unlock() 84 if proposalInfo.removed { 85 return fmt.Errorf("UpdateProposal on a removed proposal: %x", proposalHash) 86 } 87 88 proposalInfo.ballot = ballot 89 proposalInfo.updated = true 90 return nil 91 } 92 93 func (cache *Cache) RemoveProposal(proposalHash []byte) error { 94 proposalInfo, err := cache.get(proposalHash) 95 if err != nil { 96 return err 97 } 98 proposalInfo.Lock() 99 defer proposalInfo.Unlock() 100 if proposalInfo.removed { 101 return fmt.Errorf("RemoveProposal on removed proposal: %x", proposalHash) 102 } 103 proposalInfo.removed = true 104 return nil 105 } 106 107 // Writes whatever is in the cache to the output Writer state. Does not flush the cache, to do that call Reset() 108 // after Sync or use Flush if your wish to use the output state as your next backend 109 func (cache *Cache) Sync(state Writer) error { 110 cache.Lock() 111 defer cache.Unlock() 112 var hashes ProposalHashArray 113 for hash := range cache.proposals { 114 hashes = append(hashes, hash) 115 } 116 sort.Stable(hashes) 117 118 // Update or delete proposals 119 for _, hash := range hashes { 120 proposalInfo := cache.proposals[hash] 121 proposalInfo.RLock() 122 if proposalInfo.removed { 123 err := state.RemoveProposal(hash[:]) 124 if err != nil { 125 proposalInfo.RUnlock() 126 return err 127 } 128 } else if proposalInfo.updated { 129 err := state.UpdateProposal(hash[:], proposalInfo.ballot) 130 if err != nil { 131 proposalInfo.RUnlock() 132 return err 133 } 134 } 135 proposalInfo.RUnlock() 136 } 137 return nil 138 } 139 140 // Resets the cache to empty initialising the backing map to the same size as the previous iteration 141 func (cache *Cache) Reset(backend Reader) { 142 cache.Lock() 143 defer cache.Unlock() 144 cache.backend = backend 145 cache.proposals = make(map[[sha256.Size]byte]*proposalInfo) 146 } 147 148 func (cache *Cache) Backend() Reader { 149 return cache.backend 150 } 151 152 // Get the cache accountInfo item creating it if necessary 153 func (cache *Cache) get(proposalHash []byte) (*proposalInfo, error) { 154 var hash ProposalHash 155 copy(hash[:], proposalHash) 156 cache.RLock() 157 propInfo := cache.proposals[hash] 158 cache.RUnlock() 159 if propInfo == nil { 160 cache.Lock() 161 defer cache.Unlock() 162 propInfo = cache.proposals[hash] 163 if propInfo == nil { 164 prop, err := cache.backend.GetProposal(proposalHash) 165 if err != nil { 166 return nil, err 167 } 168 propInfo = &proposalInfo{ 169 ballot: prop, 170 } 171 cache.proposals[hash] = propInfo 172 } 173 } 174 return propInfo, nil 175 }