forked from cerc-io/plugeth
common/prque: generic priority queue (#26290)
* common, core, eth, les, trie: make prque generic * les/vflux/server: fixed issues in priorityPool * common, core, eth, les, trie: make priority also generic in prque * les/flowcontrol: add test case for priority accumulator overflow * les/flowcontrol: avoid priority value overflow * common/prque: use int priority in some tests No need to convert to int64 when we can just change the type used by the queue. * common/prque: remove comment about int64 range --------- Co-authored-by: Zsolt Felfoldi <zsfelfoldi@gmail.com> Co-authored-by: Felix Lange <fjl@twurst.com>
This commit is contained in:
co-authored by
Zsolt Felfoldi
Felix Lange
parent
6a148dd5c3
commit
bf1798e04e
@@ -91,8 +91,8 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
|
||||
}
|
||||
}()
|
||||
ordering := make(map[*eth.Request]int)
|
||||
timeouts := prque.New(func(data interface{}, index int) {
|
||||
ordering[data.(*eth.Request)] = index
|
||||
timeouts := prque.New[int64, *eth.Request](func(data *eth.Request, index int) {
|
||||
ordering[data] = index
|
||||
})
|
||||
|
||||
timeout := time.NewTimer(0)
|
||||
@@ -268,14 +268,12 @@ func (d *Downloader) concurrentFetch(queue typedQueue, beaconMode bool) error {
|
||||
// below is purely for to catch programming errors, given the correct
|
||||
// code, there's no possible order of events that should result in a
|
||||
// timeout firing for a non-existent event.
|
||||
item, exp := timeouts.Peek()
|
||||
req, exp := timeouts.Peek()
|
||||
if now, at := time.Now(), time.Unix(0, -exp); now.Before(at) {
|
||||
log.Error("Timeout triggered but not reached", "left", at.Sub(now))
|
||||
timeout.Reset(at.Sub(now))
|
||||
continue
|
||||
}
|
||||
req := item.(*eth.Request)
|
||||
|
||||
// Stop tracking the timed out request from a timing perspective,
|
||||
// cancel it, so it's not considered in-flight anymore, but keep
|
||||
// the peer marked busy to prevent assigning a second request and
|
||||
|
||||
+25
-23
@@ -115,7 +115,7 @@ type queue struct {
|
||||
// Headers are "special", they download in batches, supported by a skeleton chain
|
||||
headerHead common.Hash // Hash of the last queued header to verify order
|
||||
headerTaskPool map[uint64]*types.Header // Pending header retrieval tasks, mapping starting indexes to skeleton headers
|
||||
headerTaskQueue *prque.Prque // Priority queue of the skeleton indexes to fetch the filling headers for
|
||||
headerTaskQueue *prque.Prque[int64, uint64] // Priority queue of the skeleton indexes to fetch the filling headers for
|
||||
headerPeerMiss map[string]map[uint64]struct{} // Set of per-peer header batches known to be unavailable
|
||||
headerPendPool map[string]*fetchRequest // Currently pending header retrieval operations
|
||||
headerResults []*types.Header // Result cache accumulating the completed headers
|
||||
@@ -125,15 +125,15 @@ type queue struct {
|
||||
headerContCh chan bool // Channel to notify when header download finishes
|
||||
|
||||
// All data retrievals below are based on an already assembles header chain
|
||||
blockTaskPool map[common.Hash]*types.Header // Pending block (body) retrieval tasks, mapping hashes to headers
|
||||
blockTaskQueue *prque.Prque // Priority queue of the headers to fetch the blocks (bodies) for
|
||||
blockPendPool map[string]*fetchRequest // Currently pending block (body) retrieval operations
|
||||
blockWakeCh chan bool // Channel to notify the block fetcher of new tasks
|
||||
blockTaskPool map[common.Hash]*types.Header // Pending block (body) retrieval tasks, mapping hashes to headers
|
||||
blockTaskQueue *prque.Prque[int64, *types.Header] // Priority queue of the headers to fetch the blocks (bodies) for
|
||||
blockPendPool map[string]*fetchRequest // Currently pending block (body) retrieval operations
|
||||
blockWakeCh chan bool // Channel to notify the block fetcher of new tasks
|
||||
|
||||
receiptTaskPool map[common.Hash]*types.Header // Pending receipt retrieval tasks, mapping hashes to headers
|
||||
receiptTaskQueue *prque.Prque // Priority queue of the headers to fetch the receipts for
|
||||
receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations
|
||||
receiptWakeCh chan bool // Channel to notify when receipt fetcher of new tasks
|
||||
receiptTaskPool map[common.Hash]*types.Header // Pending receipt retrieval tasks, mapping hashes to headers
|
||||
receiptTaskQueue *prque.Prque[int64, *types.Header] // Priority queue of the headers to fetch the receipts for
|
||||
receiptPendPool map[string]*fetchRequest // Currently pending receipt retrieval operations
|
||||
receiptWakeCh chan bool // Channel to notify when receipt fetcher of new tasks
|
||||
|
||||
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
||||
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
||||
@@ -150,9 +150,9 @@ func newQueue(blockCacheLimit int, thresholdInitialSize int) *queue {
|
||||
lock := new(sync.RWMutex)
|
||||
q := &queue{
|
||||
headerContCh: make(chan bool, 1),
|
||||
blockTaskQueue: prque.New(nil),
|
||||
blockTaskQueue: prque.New[int64, *types.Header](nil),
|
||||
blockWakeCh: make(chan bool, 1),
|
||||
receiptTaskQueue: prque.New(nil),
|
||||
receiptTaskQueue: prque.New[int64, *types.Header](nil),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
active: sync.NewCond(lock),
|
||||
lock: lock,
|
||||
@@ -258,7 +258,7 @@ func (q *queue) ScheduleSkeleton(from uint64, skeleton []*types.Header) {
|
||||
}
|
||||
// Schedule all the header retrieval tasks for the skeleton assembly
|
||||
q.headerTaskPool = make(map[uint64]*types.Header)
|
||||
q.headerTaskQueue = prque.New(nil)
|
||||
q.headerTaskQueue = prque.New[int64, uint64](nil)
|
||||
q.headerPeerMiss = make(map[string]map[uint64]struct{}) // Reset availability to correct invalid chains
|
||||
q.headerResults = make([]*types.Header, len(skeleton)*MaxHeaderFetch)
|
||||
q.headerHashes = make([]common.Hash, len(skeleton)*MaxHeaderFetch)
|
||||
@@ -428,12 +428,12 @@ func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest {
|
||||
for send == 0 && !q.headerTaskQueue.Empty() {
|
||||
from, _ := q.headerTaskQueue.Pop()
|
||||
if q.headerPeerMiss[p.id] != nil {
|
||||
if _, ok := q.headerPeerMiss[p.id][from.(uint64)]; ok {
|
||||
skip = append(skip, from.(uint64))
|
||||
if _, ok := q.headerPeerMiss[p.id][from]; ok {
|
||||
skip = append(skip, from)
|
||||
continue
|
||||
}
|
||||
}
|
||||
send = from.(uint64)
|
||||
send = from
|
||||
}
|
||||
// Merge all the skipped batches back
|
||||
for _, from := range skip {
|
||||
@@ -485,7 +485,7 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo
|
||||
// item - the fetchRequest
|
||||
// progress - whether any progress was made
|
||||
// throttle - if the caller should throttle for a while
|
||||
func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque,
|
||||
func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque[int64, *types.Header],
|
||||
pendPool map[string]*fetchRequest, kind uint) (*fetchRequest, bool, bool) {
|
||||
// Short circuit if the pool has been depleted, or if the peer's already
|
||||
// downloading something (sanity check not to corrupt state)
|
||||
@@ -503,8 +503,8 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||
for proc := 0; len(send) < count && !taskQueue.Empty(); proc++ {
|
||||
// the task queue will pop items in order, so the highest prio block
|
||||
// is also the lowest block number.
|
||||
h, _ := taskQueue.Peek()
|
||||
header := h.(*types.Header)
|
||||
header, _ := taskQueue.Peek()
|
||||
|
||||
// we can ask the resultcache if this header is within the
|
||||
// "prioritized" segment of blocks. If it is not, we need to throttle
|
||||
|
||||
@@ -627,12 +627,14 @@ func (q *queue) ExpireReceipts(peer string) int {
|
||||
}
|
||||
|
||||
// expire is the generic check that moves a specific expired task from a pending
|
||||
// pool back into a task pool.
|
||||
// pool back into a task pool. The syntax on the passed taskQueue is a bit weird
|
||||
// as we would need a generic expire method to handle both types, but that is not
|
||||
// supported at the moment at least (Go 1.19).
|
||||
//
|
||||
// Note, this method expects the queue lock to be already held. The reason the
|
||||
// lock is not obtained in here is that the parameters already need to access
|
||||
// the queue, so they already need a lock anyway.
|
||||
func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue *prque.Prque) int {
|
||||
func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue interface{}) int {
|
||||
// Retrieve the request being expired and log an error if it's non-existent,
|
||||
// as there's no order of events that should lead to such expirations.
|
||||
req := pendPool[peer]
|
||||
@@ -644,10 +646,10 @@ func (q *queue) expire(peer string, pendPool map[string]*fetchRequest, taskQueue
|
||||
|
||||
// Return any non-satisfied requests to the pool
|
||||
if req.From > 0 {
|
||||
taskQueue.Push(req.From, -int64(req.From))
|
||||
taskQueue.(*prque.Prque[int64, uint64]).Push(req.From, -int64(req.From))
|
||||
}
|
||||
for _, header := range req.Headers {
|
||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||
taskQueue.(*prque.Prque[int64, *types.Header]).Push(header, -int64(header.Number.Uint64()))
|
||||
}
|
||||
return len(req.Headers)
|
||||
}
|
||||
@@ -824,7 +826,7 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt, recei
|
||||
// reason this lock is not obtained in here is because the parameters already need
|
||||
// to access the queue, so they already need a lock anyway.
|
||||
func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||
taskQueue *prque.Prque, pendPool map[string]*fetchRequest,
|
||||
taskQueue *prque.Prque[int64, *types.Header], pendPool map[string]*fetchRequest,
|
||||
reqTimer metrics.Timer, resInMeter metrics.Meter, resDropMeter metrics.Meter,
|
||||
results int, validate func(index int, header *types.Header) error,
|
||||
reconstruct func(index int, result *fetchResult)) (int, error) {
|
||||
|
||||
@@ -175,9 +175,9 @@ type BlockFetcher struct {
|
||||
completing map[common.Hash]*blockAnnounce // Blocks with headers, currently body-completing
|
||||
|
||||
// Block cache
|
||||
queue *prque.Prque // Queue containing the import operations (block number sorted)
|
||||
queues map[string]int // Per peer block counts to prevent memory exhaustion
|
||||
queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports)
|
||||
queue *prque.Prque[int64, *blockOrHeaderInject] // Queue containing the import operations (block number sorted)
|
||||
queues map[string]int // Per peer block counts to prevent memory exhaustion
|
||||
queued map[common.Hash]*blockOrHeaderInject // Set of already queued blocks (to dedup imports)
|
||||
|
||||
// Callbacks
|
||||
getHeader HeaderRetrievalFn // Retrieves a header from the local chain
|
||||
@@ -212,7 +212,7 @@ func NewBlockFetcher(light bool, getHeader HeaderRetrievalFn, getBlock blockRetr
|
||||
fetching: make(map[common.Hash]*blockAnnounce),
|
||||
fetched: make(map[common.Hash][]*blockAnnounce),
|
||||
completing: make(map[common.Hash]*blockAnnounce),
|
||||
queue: prque.New(nil),
|
||||
queue: prque.New[int64, *blockOrHeaderInject](nil),
|
||||
queues: make(map[string]int),
|
||||
queued: make(map[common.Hash]*blockOrHeaderInject),
|
||||
getHeader: getHeader,
|
||||
@@ -351,7 +351,7 @@ func (f *BlockFetcher) loop() {
|
||||
// Import any queued blocks that could potentially fit
|
||||
height := f.chainHeight()
|
||||
for !f.queue.Empty() {
|
||||
op := f.queue.PopItem().(*blockOrHeaderInject)
|
||||
op := f.queue.PopItem()
|
||||
hash := op.hash()
|
||||
if f.queueChangeHook != nil {
|
||||
f.queueChangeHook(hash, false)
|
||||
|
||||
Reference in New Issue
Block a user