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