github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/execution/registry/cache.go (about) 1 // Copyright Monax Industries Limited 2 // SPDX-License-Identifier: Apache-2.0 3 4 package registry 5 6 import ( 7 "fmt" 8 "sync" 9 10 "github.com/hyperledger/burrow/crypto" 11 ) 12 13 // Cache helps prevent unnecessary IAVLTree updates and garbage generation. 14 type Cache struct { 15 sync.RWMutex 16 backend Reader 17 registry map[crypto.Address]*nodeInfo 18 stats NodeStats 19 } 20 21 type nodeInfo struct { 22 sync.RWMutex 23 node *NodeIdentity 24 removed bool 25 updated bool 26 } 27 28 var _ Writer = &Cache{} 29 30 // NewCache returns a Cache which can write to an output Writer via Sync. 31 // Not goroutine safe, use syncStateCache if you need concurrent access 32 func NewCache(backend Reader) *Cache { 33 return &Cache{ 34 backend: backend, 35 registry: make(map[crypto.Address]*nodeInfo), 36 stats: NewNodeStats(), 37 } 38 } 39 40 func (cache *Cache) GetNodeByID(id crypto.Address) (*NodeIdentity, error) { 41 info, err := cache.get(id) 42 if err != nil { 43 return nil, err 44 } 45 info.RLock() 46 defer info.RUnlock() 47 if info.removed { 48 return nil, nil 49 } 50 return info.node, nil 51 } 52 53 func (cache *Cache) GetNodeIDsByAddress(net string) ([]crypto.Address, error) { 54 return cache.stats.GetAddresses(net), nil 55 } 56 57 func (cache *Cache) GetNumPeers() int { 58 return len(cache.registry) 59 } 60 61 func (cache *Cache) UpdateNode(id crypto.Address, node *NodeIdentity) error { 62 info, err := cache.get(id) 63 if err != nil { 64 return err 65 } 66 info.Lock() 67 defer info.Unlock() 68 if info.removed { 69 return fmt.Errorf("UpdateNode on a removed node: %x", id) 70 } 71 72 info.node = node 73 info.updated = true 74 cache.stats.Remove(info.node) 75 cache.stats.Insert(node.GetNetworkAddress(), id) 76 return nil 77 } 78 79 func (cache *Cache) RemoveNode(id crypto.Address) error { 80 info, err := cache.get(id) 81 if err != nil { 82 return err 83 } 84 info.Lock() 85 defer info.Unlock() 86 if info.removed { 87 return fmt.Errorf("RemoveNode on removed node: %x", id) 88 } 89 cache.stats.Remove(info.node) 90 info.removed = true 91 return nil 92 } 93 94 // Sync writes whatever is in the cache to the output state. Does not flush the cache, to do that call Reset() 95 // after Sync or use Flush if your wish to use the output state as your next backend 96 func (cache *Cache) Sync(state Writer) error { 97 cache.Lock() 98 defer cache.Unlock() 99 100 for id, info := range cache.registry { 101 info.RLock() 102 if info.removed { 103 err := state.RemoveNode(id) 104 if err != nil { 105 info.RUnlock() 106 return err 107 } 108 } else if info.updated { 109 err := state.UpdateNode(id, info.node) 110 if err != nil { 111 info.RUnlock() 112 return err 113 } 114 } 115 info.RUnlock() 116 } 117 return nil 118 } 119 120 // Reset the cache to empty initialising the backing map to the same size as the previous iteration 121 func (cache *Cache) Reset(backend Reader) { 122 cache.Lock() 123 defer cache.Unlock() 124 cache.backend = backend 125 cache.registry = make(map[crypto.Address]*nodeInfo) 126 } 127 128 func (cache *Cache) Backend() Reader { 129 return cache.backend 130 } 131 132 func (cache *Cache) get(id crypto.Address) (*nodeInfo, error) { 133 cache.RLock() 134 info := cache.registry[id] 135 cache.RUnlock() 136 if info == nil { 137 cache.Lock() 138 defer cache.Unlock() 139 info = cache.registry[id] 140 if info == nil { 141 node, err := cache.backend.GetNodeByID(id) 142 if err != nil { 143 return nil, err 144 } 145 info = &nodeInfo{ 146 node: node, 147 } 148 cache.registry[id] = info 149 } 150 } 151 return info, nil 152 }