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  }