github.com/codysnider/go-ethereum@v1.10.18-0.20220420071915-14f4ae99222a/eth/downloader/resultstore.go (about)

     1  // Copyright 2019 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 downloader
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"sync/atomic"
    23  
    24  	"github.com/ethereum/go-ethereum/core/types"
    25  )
    26  
    27  // resultStore implements a structure for maintaining fetchResults, tracking their
    28  // download-progress and delivering (finished) results.
    29  type resultStore struct {
    30  	items        []*fetchResult // Downloaded but not yet delivered fetch results
    31  	resultOffset uint64         // Offset of the first cached fetch result in the block chain
    32  
    33  	// Internal index of first non-completed entry, updated atomically when needed.
    34  	// If all items are complete, this will equal length(items), so
    35  	// *important* : is not safe to use for indexing without checking against length
    36  	indexIncomplete int32 // atomic access
    37  
    38  	// throttleThreshold is the limit up to which we _want_ to fill the
    39  	// results. If blocks are large, we want to limit the results to less
    40  	// than the number of available slots, and maybe only fill 1024 out of
    41  	// 8192 possible places. The queue will, at certain times, recalibrate
    42  	// this index.
    43  	throttleThreshold uint64
    44  
    45  	lock sync.RWMutex
    46  }
    47  
    48  func newResultStore(size int) *resultStore {
    49  	return &resultStore{
    50  		resultOffset:      0,
    51  		items:             make([]*fetchResult, size),
    52  		throttleThreshold: uint64(size),
    53  	}
    54  }
    55  
    56  // SetThrottleThreshold updates the throttling threshold based on the requested
    57  // limit and the total queue capacity. It returns the (possibly capped) threshold
    58  func (r *resultStore) SetThrottleThreshold(threshold uint64) uint64 {
    59  	r.lock.Lock()
    60  	defer r.lock.Unlock()
    61  
    62  	limit := uint64(len(r.items))
    63  	if threshold >= limit {
    64  		threshold = limit
    65  	}
    66  	r.throttleThreshold = threshold
    67  	return r.throttleThreshold
    68  }
    69  
    70  // AddFetch adds a header for body/receipt fetching. This is used when the queue
    71  // wants to reserve headers for fetching.
    72  //
    73  // It returns the following:
    74  //   stale     - if true, this item is already passed, and should not be requested again
    75  //   throttled - if true, the store is at capacity, this particular header is not prio now
    76  //   item      - the result to store data into
    77  //   err       - any error that occurred
    78  func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) {
    79  	r.lock.Lock()
    80  	defer r.lock.Unlock()
    81  
    82  	var index int
    83  	item, index, stale, throttled, err = r.getFetchResult(header.Number.Uint64())
    84  	if err != nil || stale || throttled {
    85  		return stale, throttled, item, err
    86  	}
    87  	if item == nil {
    88  		item = newFetchResult(header, fastSync)
    89  		r.items[index] = item
    90  	}
    91  	return stale, throttled, item, err
    92  }
    93  
    94  // GetDeliverySlot returns the fetchResult for the given header. If the 'stale' flag
    95  // is true, that means the header has already been delivered 'upstream'. This method
    96  // does not bubble up the 'throttle' flag, since it's moot at the point in time when
    97  // the item is downloaded and ready for delivery
    98  func (r *resultStore) GetDeliverySlot(headerNumber uint64) (*fetchResult, bool, error) {
    99  	r.lock.RLock()
   100  	defer r.lock.RUnlock()
   101  
   102  	res, _, stale, _, err := r.getFetchResult(headerNumber)
   103  	return res, stale, err
   104  }
   105  
   106  // getFetchResult returns the fetchResult corresponding to the given item, and
   107  // the index where the result is stored.
   108  func (r *resultStore) getFetchResult(headerNumber uint64) (item *fetchResult, index int, stale, throttle bool, err error) {
   109  	index = int(int64(headerNumber) - int64(r.resultOffset))
   110  	throttle = index >= int(r.throttleThreshold)
   111  	stale = index < 0
   112  
   113  	if index >= len(r.items) {
   114  		err = fmt.Errorf("%w: index allocation went beyond available resultStore space "+
   115  			"(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d", errInvalidChain,
   116  			index, headerNumber, r.resultOffset, len(r.items))
   117  		return nil, index, stale, throttle, err
   118  	}
   119  	if stale {
   120  		return nil, index, stale, throttle, nil
   121  	}
   122  	item = r.items[index]
   123  	return item, index, stale, throttle, nil
   124  }
   125  
   126  // hasCompletedItems returns true if there are processable items available
   127  // this method is cheaper than countCompleted
   128  func (r *resultStore) HasCompletedItems() bool {
   129  	r.lock.RLock()
   130  	defer r.lock.RUnlock()
   131  
   132  	if len(r.items) == 0 {
   133  		return false
   134  	}
   135  	if item := r.items[0]; item != nil && item.AllDone() {
   136  		return true
   137  	}
   138  	return false
   139  }
   140  
   141  // countCompleted returns the number of items ready for delivery, stopping at
   142  // the first non-complete item.
   143  //
   144  // The mthod assumes (at least) rlock is held.
   145  func (r *resultStore) countCompleted() int {
   146  	// We iterate from the already known complete point, and see
   147  	// if any more has completed since last count
   148  	index := atomic.LoadInt32(&r.indexIncomplete)
   149  	for ; ; index++ {
   150  		if index >= int32(len(r.items)) {
   151  			break
   152  		}
   153  		result := r.items[index]
   154  		if result == nil || !result.AllDone() {
   155  			break
   156  		}
   157  	}
   158  	atomic.StoreInt32(&r.indexIncomplete, index)
   159  	return int(index)
   160  }
   161  
   162  // GetCompleted returns the next batch of completed fetchResults
   163  func (r *resultStore) GetCompleted(limit int) []*fetchResult {
   164  	r.lock.Lock()
   165  	defer r.lock.Unlock()
   166  
   167  	completed := r.countCompleted()
   168  	if limit > completed {
   169  		limit = completed
   170  	}
   171  	results := make([]*fetchResult, limit)
   172  	copy(results, r.items[:limit])
   173  
   174  	// Delete the results from the cache and clear the tail.
   175  	copy(r.items, r.items[limit:])
   176  	for i := len(r.items) - limit; i < len(r.items); i++ {
   177  		r.items[i] = nil
   178  	}
   179  	// Advance the expected block number of the first cache entry
   180  	r.resultOffset += uint64(limit)
   181  	atomic.AddInt32(&r.indexIncomplete, int32(-limit))
   182  
   183  	return results
   184  }
   185  
   186  // Prepare initialises the offset with the given block number
   187  func (r *resultStore) Prepare(offset uint64) {
   188  	r.lock.Lock()
   189  	defer r.lock.Unlock()
   190  
   191  	if r.resultOffset < offset {
   192  		r.resultOffset = offset
   193  	}
   194  }