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