github.com/dominant-strategies/go-quai@v0.28.2/core/state/trie_prefetcher.go (about) 1 // Copyright 2020 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package state 18 19 import ( 20 "sync" 21 22 "github.com/dominant-strategies/go-quai/common" 23 "github.com/dominant-strategies/go-quai/log" 24 "github.com/dominant-strategies/go-quai/metrics" 25 ) 26 27 var ( 28 // triePrefetchMetricsPrefix is the prefix under which to publis the metrics. 29 triePrefetchMetricsPrefix = "trie/prefetch/" 30 ) 31 32 // triePrefetcher is an active prefetcher, which receives accounts or storage 33 // items and does trie-loading of them. The goal is to get as much useful content 34 // into the caches as possible. 35 // 36 // Note, the prefetcher's API is not thread safe. 37 type triePrefetcher struct { 38 db Database // Database to fetch trie nodes through 39 root common.Hash // Root hash of theaccount trie for metrics 40 fetches map[common.Hash]Trie // Partially or fully fetcher tries 41 fetchers map[common.Hash]*subfetcher // Subfetchers for each trie 42 43 deliveryMissMeter metrics.Meter 44 accountLoadMeter metrics.Meter 45 accountDupMeter metrics.Meter 46 accountSkipMeter metrics.Meter 47 accountWasteMeter metrics.Meter 48 storageLoadMeter metrics.Meter 49 storageDupMeter metrics.Meter 50 storageSkipMeter metrics.Meter 51 storageWasteMeter metrics.Meter 52 } 53 54 // newTriePrefetcher 55 func newTriePrefetcher(db Database, root common.Hash, namespace string) *triePrefetcher { 56 prefix := triePrefetchMetricsPrefix + namespace 57 p := &triePrefetcher{ 58 db: db, 59 root: root, 60 fetchers: make(map[common.Hash]*subfetcher), // Active prefetchers use the fetchers map 61 62 deliveryMissMeter: metrics.GetOrRegisterMeter(prefix+"/deliverymiss", nil), 63 accountLoadMeter: metrics.GetOrRegisterMeter(prefix+"/account/load", nil), 64 accountDupMeter: metrics.GetOrRegisterMeter(prefix+"/account/dup", nil), 65 accountSkipMeter: metrics.GetOrRegisterMeter(prefix+"/account/skip", nil), 66 accountWasteMeter: metrics.GetOrRegisterMeter(prefix+"/account/waste", nil), 67 storageLoadMeter: metrics.GetOrRegisterMeter(prefix+"/storage/load", nil), 68 storageDupMeter: metrics.GetOrRegisterMeter(prefix+"/storage/dup", nil), 69 storageSkipMeter: metrics.GetOrRegisterMeter(prefix+"/storage/skip", nil), 70 storageWasteMeter: metrics.GetOrRegisterMeter(prefix+"/storage/waste", nil), 71 } 72 return p 73 } 74 75 // close iterates over all the subfetchers, aborts any that were left spinning 76 // and reports the stats to the metrics subsystem. 77 func (p *triePrefetcher) close() { 78 for _, fetcher := range p.fetchers { 79 fetcher.abort() // safe to do multiple times 80 81 if metrics.Enabled { 82 if fetcher.root == p.root { 83 p.accountLoadMeter.Mark(int64(len(fetcher.seen))) 84 p.accountDupMeter.Mark(int64(fetcher.dups)) 85 p.accountSkipMeter.Mark(int64(len(fetcher.tasks))) 86 87 for _, key := range fetcher.used { 88 delete(fetcher.seen, string(key)) 89 } 90 p.accountWasteMeter.Mark(int64(len(fetcher.seen))) 91 } else { 92 p.storageLoadMeter.Mark(int64(len(fetcher.seen))) 93 p.storageDupMeter.Mark(int64(fetcher.dups)) 94 p.storageSkipMeter.Mark(int64(len(fetcher.tasks))) 95 96 for _, key := range fetcher.used { 97 delete(fetcher.seen, string(key)) 98 } 99 p.storageWasteMeter.Mark(int64(len(fetcher.seen))) 100 } 101 } 102 } 103 // Clear out all fetchers (will crash on a second call, deliberate) 104 p.fetchers = nil 105 } 106 107 // copy creates a deep-but-inactive copy of the trie prefetcher. Any trie data 108 // already loaded will be copied over, but no goroutines will be started. This 109 // is mostly used in the miner which creates a copy of it's actively mutated 110 // state to be sealed while it may further mutate the state. 111 func (p *triePrefetcher) copy() *triePrefetcher { 112 copy := &triePrefetcher{ 113 db: p.db, 114 root: p.root, 115 fetches: make(map[common.Hash]Trie), // Active prefetchers use the fetches map 116 117 deliveryMissMeter: p.deliveryMissMeter, 118 accountLoadMeter: p.accountLoadMeter, 119 accountDupMeter: p.accountDupMeter, 120 accountSkipMeter: p.accountSkipMeter, 121 accountWasteMeter: p.accountWasteMeter, 122 storageLoadMeter: p.storageLoadMeter, 123 storageDupMeter: p.storageDupMeter, 124 storageSkipMeter: p.storageSkipMeter, 125 storageWasteMeter: p.storageWasteMeter, 126 } 127 // If the prefetcher is already a copy, duplicate the data 128 if p.fetches != nil { 129 for root, fetch := range p.fetches { 130 copy.fetches[root] = p.db.CopyTrie(fetch) 131 } 132 return copy 133 } 134 // Otherwise we're copying an active fetcher, retrieve the current states 135 for root, fetcher := range p.fetchers { 136 copy.fetches[root] = fetcher.peek() 137 } 138 return copy 139 } 140 141 // prefetch schedules a batch of trie items to prefetch. 142 func (p *triePrefetcher) prefetch(root common.Hash, keys [][]byte) { 143 // If the prefetcher is an inactive one, bail out 144 if p.fetches != nil { 145 return 146 } 147 // Active fetcher, schedule the retrievals 148 fetcher := p.fetchers[root] 149 if fetcher == nil { 150 fetcher = newSubfetcher(p.db, root) 151 p.fetchers[root] = fetcher 152 } 153 fetcher.schedule(keys) 154 } 155 156 // trie returns the trie matching the root hash, or nil if the prefetcher doesn't 157 // have it. 158 func (p *triePrefetcher) trie(root common.Hash) Trie { 159 // If the prefetcher is inactive, return from existing deep copies 160 if p.fetches != nil { 161 trie := p.fetches[root] 162 if trie == nil { 163 p.deliveryMissMeter.Mark(1) 164 return nil 165 } 166 return p.db.CopyTrie(trie) 167 } 168 // Otherwise the prefetcher is active, bail if no trie was prefetched for this root 169 fetcher := p.fetchers[root] 170 if fetcher == nil { 171 p.deliveryMissMeter.Mark(1) 172 return nil 173 } 174 // Interrupt the prefetcher if it's by any chance still running and return 175 // a copy of any pre-loaded trie. 176 fetcher.abort() // safe to do multiple times 177 178 trie := fetcher.peek() 179 if trie == nil { 180 p.deliveryMissMeter.Mark(1) 181 return nil 182 } 183 return trie 184 } 185 186 // used marks a batch of state items used to allow creating statistics as to 187 // how useful or wasteful the prefetcher is. 188 func (p *triePrefetcher) used(root common.Hash, used [][]byte) { 189 if fetcher := p.fetchers[root]; fetcher != nil { 190 fetcher.used = used 191 } 192 } 193 194 // subfetcher is a trie fetcher goroutine responsible for pulling entries for a 195 // single trie. It is spawned when a new root is encountered and lives until the 196 // main prefetcher is paused and either all requested items are processed or if 197 // the trie being worked on is retrieved from the prefetcher. 198 type subfetcher struct { 199 db Database // Database to load trie nodes through 200 root common.Hash // Root hash of the trie to prefetch 201 trie Trie // Trie being populated with nodes 202 203 tasks [][]byte // Items queued up for retrieval 204 lock sync.Mutex // Lock protecting the task queue 205 206 wake chan struct{} // Wake channel if a new task is scheduled 207 stop chan struct{} // Channel to interrupt processing 208 term chan struct{} // Channel to signal iterruption 209 copy chan chan Trie // Channel to request a copy of the current trie 210 211 seen map[string]struct{} // Tracks the entries already loaded 212 dups int // Number of duplicate preload tasks 213 used [][]byte // Tracks the entries used in the end 214 } 215 216 // newSubfetcher creates a goroutine to prefetch state items belonging to a 217 // particular root hash. 218 func newSubfetcher(db Database, root common.Hash) *subfetcher { 219 sf := &subfetcher{ 220 db: db, 221 root: root, 222 wake: make(chan struct{}, 1), 223 stop: make(chan struct{}), 224 term: make(chan struct{}), 225 copy: make(chan chan Trie), 226 seen: make(map[string]struct{}), 227 } 228 go sf.loop() 229 return sf 230 } 231 232 // schedule adds a batch of trie keys to the queue to prefetch. 233 func (sf *subfetcher) schedule(keys [][]byte) { 234 // Append the tasks to the current queue 235 sf.lock.Lock() 236 sf.tasks = append(sf.tasks, keys...) 237 sf.lock.Unlock() 238 239 // Notify the prefetcher, it's fine if it's already terminated 240 select { 241 case sf.wake <- struct{}{}: 242 default: 243 } 244 } 245 246 // peek tries to retrieve a deep copy of the fetcher's trie in whatever form it 247 // is currently. 248 func (sf *subfetcher) peek() Trie { 249 ch := make(chan Trie) 250 select { 251 case sf.copy <- ch: 252 // Subfetcher still alive, return copy from it 253 return <-ch 254 255 case <-sf.term: 256 // Subfetcher already terminated, return a copy directly 257 if sf.trie == nil { 258 return nil 259 } 260 return sf.db.CopyTrie(sf.trie) 261 } 262 } 263 264 // abort interrupts the subfetcher immediately. It is safe to call abort multiple 265 // times but it is not thread safe. 266 func (sf *subfetcher) abort() { 267 select { 268 case <-sf.stop: 269 default: 270 close(sf.stop) 271 } 272 <-sf.term 273 } 274 275 // loop waits for new tasks to be scheduled and keeps loading them until it runs 276 // out of tasks or its underlying trie is retrieved for committing. 277 func (sf *subfetcher) loop() { 278 // No matter how the loop stops, signal anyone waiting that it's terminated 279 defer close(sf.term) 280 281 // Start by opening the trie and stop processing if it fails 282 trie, err := sf.db.OpenTrie(sf.root) 283 if err != nil { 284 log.Warn("Trie prefetcher failed opening trie", "root", sf.root, "err", err) 285 return 286 } 287 sf.trie = trie 288 289 // Trie opened successfully, keep prefetching items 290 for { 291 select { 292 case <-sf.wake: 293 // Subfetcher was woken up, retrieve any tasks to avoid spinning the lock 294 sf.lock.Lock() 295 tasks := sf.tasks 296 sf.tasks = nil 297 sf.lock.Unlock() 298 299 // Prefetch any tasks until the loop is interrupted 300 for i, task := range tasks { 301 select { 302 case <-sf.stop: 303 // If termination is requested, add any leftover back and return 304 sf.lock.Lock() 305 sf.tasks = append(sf.tasks, tasks[i:]...) 306 sf.lock.Unlock() 307 return 308 309 case ch := <-sf.copy: 310 // Somebody wants a copy of the current trie, grant them 311 ch <- sf.db.CopyTrie(sf.trie) 312 313 default: 314 // No termination request yet, prefetch the next entry 315 if _, ok := sf.seen[string(task)]; ok { 316 sf.dups++ 317 } else { 318 sf.trie.TryGet(task) 319 sf.seen[string(task)] = struct{}{} 320 } 321 } 322 } 323 324 case ch := <-sf.copy: 325 // Somebody wants a copy of the current trie, grant them 326 ch <- sf.db.CopyTrie(sf.trie) 327 328 case <-sf.stop: 329 // Termination is requested, abort and leave remaining tasks 330 return 331 } 332 } 333 }