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  }