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