github.com/datachainlab/burrow@v0.25.0/execution/proposal/cache.go (about) 1 // Copyright 2017 Monax Industries Limited 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package proposal 16 17 import ( 18 "bytes" 19 "crypto/sha256" 20 "fmt" 21 "sort" 22 "sync" 23 24 "github.com/hyperledger/burrow/txs/payload" 25 ) 26 27 // The Cache helps prevent unnecessary IAVLTree updates and garbage generation. 28 type Cache struct { 29 sync.RWMutex 30 backend Reader 31 proposals map[[sha256.Size]byte]*proposalInfo 32 } 33 34 type proposalInfo struct { 35 sync.RWMutex 36 ballot *payload.Ballot 37 removed bool 38 updated bool 39 } 40 41 type ProposalHash [sha256.Size]byte 42 43 type ProposalHashArray []ProposalHash 44 45 func (p ProposalHashArray) Len() int { 46 return len(p) 47 } 48 49 func (p ProposalHashArray) Swap(i, j int) { 50 p[j], p[i] = p[i], p[j] 51 } 52 53 func (p ProposalHashArray) Less(i, j int) bool { 54 switch bytes.Compare(p[i][:], p[j][:]) { 55 case -1: 56 return true 57 case 0, 1: 58 return false 59 default: 60 panic("bytes.Compare returned invalid value") 61 } 62 } 63 64 var _ Writer = &Cache{} 65 66 // Returns a Cache that wraps an underlying NameRegCacheGetter to use on a cache miss, can write to an 67 // output Writer via Sync. 68 // Not goroutine safe, use syncStateCache if you need concurrent access 69 func NewCache(backend Reader) *Cache { 70 return &Cache{ 71 backend: backend, 72 proposals: make(map[[sha256.Size]byte]*proposalInfo), 73 } 74 } 75 76 func (cache *Cache) GetProposal(proposalHash []byte) (*payload.Ballot, error) { 77 proposalInfo, err := cache.get(proposalHash) 78 if err != nil { 79 return nil, err 80 } 81 proposalInfo.RLock() 82 defer proposalInfo.RUnlock() 83 if proposalInfo.removed { 84 return nil, nil 85 } 86 return proposalInfo.ballot, nil 87 } 88 89 func (cache *Cache) UpdateProposal(proposalHash []byte, ballot *payload.Ballot) error { 90 proposalInfo, err := cache.get(proposalHash) 91 if err != nil { 92 return err 93 } 94 proposalInfo.Lock() 95 defer proposalInfo.Unlock() 96 if proposalInfo.removed { 97 return fmt.Errorf("UpdateProposal on a removed proposal: %x", proposalHash) 98 } 99 100 proposalInfo.ballot = ballot 101 proposalInfo.updated = true 102 return nil 103 } 104 105 func (cache *Cache) RemoveProposal(proposalHash []byte) error { 106 proposalInfo, err := cache.get(proposalHash) 107 if err != nil { 108 return err 109 } 110 proposalInfo.Lock() 111 defer proposalInfo.Unlock() 112 if proposalInfo.removed { 113 return fmt.Errorf("RemoveProposal on removed proposal: %x", proposalHash) 114 } 115 proposalInfo.removed = true 116 return nil 117 } 118 119 // Writes whatever is in the cache to the output Writer state. Does not flush the cache, to do that call Reset() 120 // after Sync or use Flusth if your wish to use the output state as your next backend 121 func (cache *Cache) Sync(state Writer) error { 122 cache.Lock() 123 defer cache.Unlock() 124 // Determine order for names 125 // note names may be of any length less than some limit 126 var hashes ProposalHashArray 127 for hash := range cache.proposals { 128 hashes = append(hashes, hash) 129 } 130 sort.Stable(hashes) 131 132 // Update or delete names. 133 for _, hash := range hashes { 134 proposalInfo := cache.proposals[hash] 135 proposalInfo.RLock() 136 if proposalInfo.removed { 137 err := state.RemoveProposal(hash[:]) 138 if err != nil { 139 proposalInfo.RUnlock() 140 return err 141 } 142 } else if proposalInfo.updated { 143 err := state.UpdateProposal(hash[:], proposalInfo.ballot) 144 if err != nil { 145 proposalInfo.RUnlock() 146 return err 147 } 148 } 149 proposalInfo.RUnlock() 150 } 151 return nil 152 } 153 154 // Resets the cache to empty initialising the backing map to the same size as the previous iteration. 155 func (cache *Cache) Reset(backend Reader) { 156 cache.Lock() 157 defer cache.Unlock() 158 cache.backend = backend 159 cache.proposals = make(map[[sha256.Size]byte]*proposalInfo) 160 } 161 162 // Syncs the Cache and Resets it to use Writer as the backend Reader 163 func (cache *Cache) Flush(output Writer, backend Reader) error { 164 err := cache.Sync(output) 165 if err != nil { 166 return err 167 } 168 cache.Reset(backend) 169 return nil 170 } 171 172 func (cache *Cache) Backend() Reader { 173 return cache.backend 174 } 175 176 // Get the cache accountInfo item creating it if necessary 177 func (cache *Cache) get(proposalHash []byte) (*proposalInfo, error) { 178 var hash ProposalHash 179 copy(hash[:], proposalHash) 180 cache.RLock() 181 propInfo := cache.proposals[hash] 182 cache.RUnlock() 183 if propInfo == nil { 184 cache.Lock() 185 defer cache.Unlock() 186 propInfo = cache.proposals[hash] 187 if propInfo == nil { 188 prop, err := cache.backend.GetProposal(proposalHash) 189 if err != nil { 190 return nil, err 191 } 192 propInfo = &proposalInfo{ 193 ballot: prop, 194 } 195 cache.proposals[hash] = propInfo 196 } 197 } 198 return propInfo, nil 199 }