mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
downloader/queue: increase underlying buffer of results, new throttle mechanism
This commit is contained in:
parent
e121488447
commit
40114c9956
5 changed files with 280 additions and 316 deletions
|
|
@ -1091,9 +1091,8 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) (
|
||||||
return d.queue.DeliverHeaders(pack.peerID, pack.headers, d.headerProcCh)
|
return d.queue.DeliverHeaders(pack.peerID, pack.headers, d.headerProcCh)
|
||||||
}
|
}
|
||||||
expire = func() map[string]int { return d.queue.ExpireHeaders(d.requestTTL()) }
|
expire = func() map[string]int { return d.queue.ExpireHeaders(d.requestTTL()) }
|
||||||
throttle = func() bool { return false }
|
reserve = func(p *peerConnection, count int) (*fetchRequest, bool, bool, error) {
|
||||||
reserve = func(p *peerConnection, count int) (*fetchRequest, bool, error) {
|
return d.queue.ReserveHeaders(p, count), false, false, nil
|
||||||
return d.queue.ReserveHeaders(p, count), false, nil
|
|
||||||
}
|
}
|
||||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) }
|
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) }
|
||||||
capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.requestRTT()) }
|
capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.requestRTT()) }
|
||||||
|
|
@ -1102,7 +1101,7 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
err := d.fetchParts(d.headerCh, deliver, d.queue.headerContCh, expire,
|
err := d.fetchParts(d.headerCh, deliver, d.queue.headerContCh, expire,
|
||||||
d.queue.PendingHeaders, d.queue.InFlightHeaders, throttle, reserve,
|
d.queue.PendingHeaders, d.queue.InFlightHeaders, reserve,
|
||||||
nil, fetch, d.queue.CancelHeaders, capacity, d.peers.HeaderIdlePeers, setIdle, "headers")
|
nil, fetch, d.queue.CancelHeaders, capacity, d.peers.HeaderIdlePeers, setIdle, "headers")
|
||||||
|
|
||||||
log.Debug("Skeleton fill terminated", "err", err)
|
log.Debug("Skeleton fill terminated", "err", err)
|
||||||
|
|
@ -1128,7 +1127,7 @@ func (d *Downloader) fetchBodies(from uint64) error {
|
||||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) }
|
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) }
|
||||||
)
|
)
|
||||||
err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire,
|
err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire,
|
||||||
d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ShouldThrottleBlocks, d.queue.ReserveBodies,
|
d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ReserveBodies,
|
||||||
d.bodyFetchHook, fetch, d.queue.CancelBodies, capacity, d.peers.BodyIdlePeers, setIdle, "bodies")
|
d.bodyFetchHook, fetch, d.queue.CancelBodies, capacity, d.peers.BodyIdlePeers, setIdle, "bodies")
|
||||||
|
|
||||||
log.Debug("Block body download terminated", "err", err)
|
log.Debug("Block body download terminated", "err", err)
|
||||||
|
|
@ -1154,7 +1153,7 @@ func (d *Downloader) fetchReceipts(from uint64) error {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
err := d.fetchParts(d.receiptCh, deliver, d.receiptWakeCh, expire,
|
err := d.fetchParts(d.receiptCh, deliver, d.receiptWakeCh, expire,
|
||||||
d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ShouldThrottleReceipts, d.queue.ReserveReceipts,
|
d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ReserveReceipts,
|
||||||
d.receiptFetchHook, fetch, d.queue.CancelReceipts, capacity, d.peers.ReceiptIdlePeers, setIdle, "receipts")
|
d.receiptFetchHook, fetch, d.queue.CancelReceipts, capacity, d.peers.ReceiptIdlePeers, setIdle, "receipts")
|
||||||
|
|
||||||
log.Debug("Transaction receipt download terminated", "err", err)
|
log.Debug("Transaction receipt download terminated", "err", err)
|
||||||
|
|
@ -1187,7 +1186,7 @@ func (d *Downloader) fetchReceipts(from uint64) error {
|
||||||
// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping)
|
// - setIdle: network callback to set a peer back to idle and update its estimated capacity (traffic shaping)
|
||||||
// - kind: textual label of the type being downloaded to display in log mesages
|
// - kind: textual label of the type being downloaded to display in log mesages
|
||||||
func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool,
|
func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack) (int, error), wakeCh chan bool,
|
||||||
expire func() map[string]int, pending func() int, inFlight func() bool, throttle func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, error),
|
expire func() map[string]int, pending func() int, inFlight func() bool, reserve func(*peerConnection, int) (*fetchRequest, bool, bool, error),
|
||||||
fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int,
|
fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int,
|
||||||
idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int, time.Time), kind string) error {
|
idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int, time.Time), kind string) error {
|
||||||
|
|
||||||
|
|
@ -1306,34 +1305,32 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
|
||||||
// Send a download request to all idle peers, until throttled
|
// Send a download request to all idle peers, until throttled
|
||||||
progressed, throttled, running := false, false, inFlight()
|
progressed, throttled, running := false, false, inFlight()
|
||||||
idles, total := idle()
|
idles, total := idle()
|
||||||
|
pendCount := 1
|
||||||
for _, peer := range idles {
|
for _, peer := range idles {
|
||||||
// Short circuit if throttling activated
|
// Short circuit if throttling activated
|
||||||
if throttle() {
|
if throttled {
|
||||||
throttled = true
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Short circuit if there is no more available task.
|
// Short circuit if there is no more available task.
|
||||||
if pending() == 0 {
|
if pendCount = pending(); pendCount == 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Reserve a chunk of fetches for a peer. A nil can mean either that
|
// Reserve a chunk of fetches for a peer. A nil can mean either that
|
||||||
// no more headers are available, or that the peer is known not to
|
// no more headers are available, or that the peer is known not to
|
||||||
// have them.
|
// have them.
|
||||||
request, progress, err := reserve(peer, capacity(peer))
|
request, progress, throttle, err := reserve(peer, capacity(peer))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Info("Error in loop", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if progress {
|
if progress {
|
||||||
progressed = true
|
progressed = true
|
||||||
}
|
}
|
||||||
if request == nil {
|
if throttle {
|
||||||
// This means we've over-committed, and reserving returns nil
|
|
||||||
// because there's no space for the top-prio header to download.
|
|
||||||
// No need to continue this loop
|
|
||||||
throttled = true
|
throttled = true
|
||||||
break
|
throttleBlockCounter.Inc(1)
|
||||||
}
|
}
|
||||||
|
if request != nil {
|
||||||
if request.From > 0 {
|
if request.From > 0 {
|
||||||
peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From)
|
peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1351,11 +1348,12 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
|
||||||
// a much bigger issue.
|
// a much bigger issue.
|
||||||
panic(fmt.Sprintf("%v: %s fetch assignment failed", peer, kind))
|
panic(fmt.Sprintf("%v: %s fetch assignment failed", peer, kind))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
running = true
|
running = true
|
||||||
}
|
}
|
||||||
// Make sure that we have peers available for fetching. If all peers have been tried
|
// Make sure that we have peers available for fetching. If all peers have been tried
|
||||||
// and all failed throw an error
|
// and all failed throw an error
|
||||||
if !progressed && !throttled && !running && len(idles) == total && pending() > 0 {
|
if !progressed && !throttled && !running && len(idles) == total && pendCount > 0 {
|
||||||
return errPeersUnavailable
|
return errPeersUnavailable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1482,7 +1480,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, td *big.Int) er
|
||||||
rollbackErr = err
|
rollbackErr = err
|
||||||
rollback = append(rollback, chunk[:n]...)
|
rollback = append(rollback, chunk[:n]...)
|
||||||
}
|
}
|
||||||
log.Info("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "err", err)
|
log.Info("Invalid header encountered", "number", chunk[n].Number, "hash", chunk[n].Hash(), "err", err, "parent", chunk[n].ParentHash)
|
||||||
return errInvalidChain
|
return errInvalidChain
|
||||||
}
|
}
|
||||||
// All verifications passed, store newly found uncertain headers
|
// All verifications passed, store newly found uncertain headers
|
||||||
|
|
@ -1637,6 +1635,7 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
P, beforeP, afterP := splitAroundPivot(pivot, results)
|
P, beforeP, afterP := splitAroundPivot(pivot, results)
|
||||||
|
results = nil
|
||||||
if err := d.commitFastSyncData(beforeP, sync); err != nil {
|
if err := d.commitFastSyncData(beforeP, sync); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -1674,6 +1673,15 @@ func (d *Downloader) processFastSyncContent(latest *types.Header) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, before, after []*fetchResult) {
|
func splitAroundPivot(pivot uint64, results []*fetchResult) (p *fetchResult, before, after []*fetchResult) {
|
||||||
|
if len(results) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if lastNum := results[len(results)-1].Header.Number.Uint64(); lastNum < pivot {
|
||||||
|
// the pivot is somewhere in the future
|
||||||
|
before = results
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// This can also be optimized, but only happens very seldom
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
num := result.Header.Number.Uint64()
|
num := result.Header.Number.Uint64()
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -1704,7 +1712,7 @@ func (d *Downloader) commitFastSyncData(results []*fetchResult, stateSync *state
|
||||||
}
|
}
|
||||||
// Retrieve the a batch of results to import
|
// Retrieve the a batch of results to import
|
||||||
first, last := results[0].Header, results[len(results)-1].Header
|
first, last := results[0].Header, results[len(results)-1].Header
|
||||||
log.Info("Inserting fast-sync blocks", "items", len(results),
|
log.Debug("Inserting fast-sync blocks", "items", len(results),
|
||||||
"firstnum", first.Number, "firsthash", first.Hash(),
|
"firstnum", first.Number, "firsthash", first.Hash(),
|
||||||
"lastnumn", last.Number, "lasthash", last.Hash(),
|
"lastnumn", last.Number, "lasthash", last.Hash(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -32,6 +33,11 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
BodyType = 0
|
||||||
|
ReceiptType = 1
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
blockCacheItems = 8192 // Maximum number of blocks to cache before throttling the download
|
blockCacheItems = 8192 // Maximum number of blocks to cache before throttling the download
|
||||||
blockCacheMemory = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching
|
blockCacheMemory = 64 * 1024 * 1024 // Maximum amount of memory to use for block caching
|
||||||
|
|
@ -54,7 +60,7 @@ type fetchRequest struct {
|
||||||
// fetchResult is a struct collecting partial results from data fetchers until
|
// fetchResult is a struct collecting partial results from data fetchers until
|
||||||
// all outstanding pieces complete and the result as a whole can be processed.
|
// all outstanding pieces complete and the result as a whole can be processed.
|
||||||
type fetchResult struct {
|
type fetchResult struct {
|
||||||
Pending uint8 // Number of data fetches still pending
|
pending int32 // Flag telling what deliveries are outstanding
|
||||||
Hash common.Hash // Hash of the header to prevent recalculating
|
Hash common.Hash // Hash of the header to prevent recalculating
|
||||||
|
|
||||||
Header *types.Header
|
Header *types.Header
|
||||||
|
|
@ -63,6 +69,52 @@ type fetchResult struct {
|
||||||
Receipts types.Receipts
|
Receipts types.Receipts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newFetchResult(header *types.Header, fastSync bool) *fetchResult {
|
||||||
|
item := &fetchResult{
|
||||||
|
Hash: header.Hash(),
|
||||||
|
Header: header,
|
||||||
|
}
|
||||||
|
if !header.EmptyBody() {
|
||||||
|
item.pending = 1
|
||||||
|
}
|
||||||
|
if fastSync && !header.EmptyReceipts() {
|
||||||
|
item.pending += 2
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBodyDone flags the body as finished.
|
||||||
|
func (f *fetchResult) SetBodyDone() {
|
||||||
|
if v := atomic.LoadInt32(&f.pending); v == 1 || v == 3 {
|
||||||
|
atomic.AddInt32(&f.pending, -1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllDone checks item is done
|
||||||
|
func (f *fetchResult) AllDone() bool {
|
||||||
|
return atomic.LoadInt32(&f.pending) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReceiptsDone flags the receipts as finished.
|
||||||
|
func (f *fetchResult) SetReceiptsDone() {
|
||||||
|
if v := atomic.LoadInt32(&f.pending); v == 2 || v == 3 {
|
||||||
|
atomic.AddInt32(&f.pending, -2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckDone checks if the given type is done already
|
||||||
|
func (f *fetchResult) Done(typ int) bool {
|
||||||
|
v := atomic.LoadInt32(&f.pending)
|
||||||
|
switch typ {
|
||||||
|
case BodyType:
|
||||||
|
return !(v == 1 || v == 3)
|
||||||
|
case ReceiptType:
|
||||||
|
return !(v == 2 || v == 3)
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// queue represents hashes that are either need fetching or are being fetched
|
// queue represents hashes that are either need fetching or are being fetched
|
||||||
type queue struct {
|
type queue struct {
|
||||||
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
|
mode SyncMode // Synchronisation mode to decide on the block parts to schedule for fetching
|
||||||
|
|
@ -82,12 +134,10 @@ type queue struct {
|
||||||
blockTaskPool map[common.Hash]*types.Header // [eth/62] Pending block (body) retrieval tasks, mapping hashes to headers
|
blockTaskPool map[common.Hash]*types.Header // [eth/62] Pending block (body) retrieval tasks, mapping hashes to headers
|
||||||
blockTaskQueue *prque.Prque // [eth/62] Priority queue of the headers to fetch the blocks (bodies) for
|
blockTaskQueue *prque.Prque // [eth/62] Priority queue of the headers to fetch the blocks (bodies) for
|
||||||
blockPendPool map[string]*fetchRequest // [eth/62] Currently pending block (body) retrieval operations
|
blockPendPool map[string]*fetchRequest // [eth/62] Currently pending block (body) retrieval operations
|
||||||
blockDonePool map[common.Hash]struct{} // [eth/62] Set of the completed block (body) fetches
|
|
||||||
|
|
||||||
receiptTaskPool map[common.Hash]*types.Header // [eth/63] Pending receipt retrieval tasks, mapping hashes to headers
|
receiptTaskPool map[common.Hash]*types.Header // [eth/63] Pending receipt retrieval tasks, mapping hashes to headers
|
||||||
receiptTaskQueue *prque.Prque // [eth/63] Priority queue of the headers to fetch the receipts for
|
receiptTaskQueue *prque.Prque // [eth/63] Priority queue of the headers to fetch the receipts for
|
||||||
receiptPendPool map[string]*fetchRequest // [eth/63] Currently pending receipt retrieval operations
|
receiptPendPool map[string]*fetchRequest // [eth/63] Currently pending receipt retrieval operations
|
||||||
receiptDonePool map[common.Hash]struct{} // [eth/63] Set of the completed receipt fetches
|
|
||||||
|
|
||||||
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
resultCache *resultStore // Downloaded but not yet delivered fetch results
|
||||||
//resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
//resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
||||||
|
|
@ -107,13 +157,10 @@ func newQueue(blockCacheLimit int) *queue {
|
||||||
blockTaskPool: make(map[common.Hash]*types.Header),
|
blockTaskPool: make(map[common.Hash]*types.Header),
|
||||||
blockTaskQueue: prque.New(nil),
|
blockTaskQueue: prque.New(nil),
|
||||||
blockPendPool: make(map[string]*fetchRequest),
|
blockPendPool: make(map[string]*fetchRequest),
|
||||||
blockDonePool: make(map[common.Hash]struct{}),
|
|
||||||
receiptTaskPool: make(map[common.Hash]*types.Header),
|
receiptTaskPool: make(map[common.Hash]*types.Header),
|
||||||
receiptTaskQueue: prque.New(nil),
|
receiptTaskQueue: prque.New(nil),
|
||||||
receiptPendPool: make(map[string]*fetchRequest),
|
receiptPendPool: make(map[string]*fetchRequest),
|
||||||
receiptDonePool: make(map[common.Hash]struct{}),
|
resultCache: newResultStore(blockCacheLimit * 2),
|
||||||
//resultCache: make([]*fetchResult, blockCacheItems),
|
|
||||||
resultCache: newResultStore(blockCacheLimit),
|
|
||||||
active: sync.NewCond(lock),
|
active: sync.NewCond(lock),
|
||||||
lock: lock,
|
lock: lock,
|
||||||
}
|
}
|
||||||
|
|
@ -133,14 +180,12 @@ func (q *queue) Reset() {
|
||||||
q.blockTaskPool = make(map[common.Hash]*types.Header)
|
q.blockTaskPool = make(map[common.Hash]*types.Header)
|
||||||
q.blockTaskQueue.Reset()
|
q.blockTaskQueue.Reset()
|
||||||
q.blockPendPool = make(map[string]*fetchRequest)
|
q.blockPendPool = make(map[string]*fetchRequest)
|
||||||
q.blockDonePool = make(map[common.Hash]struct{})
|
|
||||||
|
|
||||||
q.receiptTaskPool = make(map[common.Hash]*types.Header)
|
q.receiptTaskPool = make(map[common.Hash]*types.Header)
|
||||||
q.receiptTaskQueue.Reset()
|
q.receiptTaskQueue.Reset()
|
||||||
q.receiptPendPool = make(map[string]*fetchRequest)
|
q.receiptPendPool = make(map[string]*fetchRequest)
|
||||||
q.receiptDonePool = make(map[common.Hash]struct{})
|
|
||||||
|
|
||||||
q.resultCache = newResultStore(blockCacheItems)
|
q.resultCache = newResultStore(blockCacheItems * 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close marks the end of the sync, unblocking Results.
|
// Close marks the end of the sync, unblocking Results.
|
||||||
|
|
@ -210,54 +255,8 @@ func (q *queue) Idle() bool {
|
||||||
|
|
||||||
queued := q.blockTaskQueue.Size() + q.receiptTaskQueue.Size()
|
queued := q.blockTaskQueue.Size() + q.receiptTaskQueue.Size()
|
||||||
pending := len(q.blockPendPool) + len(q.receiptPendPool)
|
pending := len(q.blockPendPool) + len(q.receiptPendPool)
|
||||||
cached := len(q.blockDonePool) + len(q.receiptDonePool)
|
|
||||||
|
|
||||||
return (queued + pending + cached) == 0
|
return (queued + pending) == 0
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldThrottleBlocks checks if the download should be throttled (active block (body)
|
|
||||||
// fetches exceed block cache).
|
|
||||||
func (q *queue) ShouldThrottleBlocks() bool {
|
|
||||||
q.lock.Lock()
|
|
||||||
t := q.resultSlots(q.blockPendPool, q.blockDonePool) <= 0
|
|
||||||
q.lock.Unlock()
|
|
||||||
if t {
|
|
||||||
throttleBlockCounter.Inc(1)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldThrottleReceipts checks if the download should be throttled (active receipt
|
|
||||||
// fetches exceed block cache).
|
|
||||||
func (q *queue) ShouldThrottleReceipts() bool {
|
|
||||||
q.lock.Lock()
|
|
||||||
t := q.resultSlots(q.receiptPendPool, q.receiptDonePool) <= 0
|
|
||||||
q.lock.Unlock()
|
|
||||||
if t {
|
|
||||||
throttleReceiptCounter.Inc(1)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// resultSlots calculates the number of results slots available for requests
|
|
||||||
// whilst adhering to both the item and the memory limit too of the results
|
|
||||||
// cache.
|
|
||||||
func (q *queue) resultSlots(pendPool map[string]*fetchRequest, donePool map[common.Hash]struct{}) int {
|
|
||||||
// Calculate the maximum length capped by the memory limit
|
|
||||||
cacheItems := len(q.resultCache.items)
|
|
||||||
limit := cacheItems
|
|
||||||
if common.StorageSize(cacheItems)*q.resultSize > common.StorageSize(blockCacheMemory) {
|
|
||||||
limit = int((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
|
|
||||||
}
|
|
||||||
// Calculate the number of slots already finished
|
|
||||||
finished := q.resultCache.countCompleted()
|
|
||||||
// Calculate the number of slots currently downloading
|
|
||||||
pending := 0
|
|
||||||
//iterations := 0
|
|
||||||
for _, request := range pendPool {
|
|
||||||
pending += len(request.Headers)
|
|
||||||
}
|
|
||||||
return limit - finished - pending
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
|
// ScheduleSkeleton adds a batch of header retrieval tasks to the queue to fill
|
||||||
|
|
@ -372,32 +371,26 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
|
||||||
|
|
||||||
// Results retrieves and permanently removes a batch of fetch results from
|
// Results retrieves and permanently removes a batch of fetch results from
|
||||||
// the cache. the result slice will be empty if the queue has been closed.
|
// the cache. the result slice will be empty if the queue has been closed.
|
||||||
|
// This is 'thread-safe', but assumes that there are not two simultaneous
|
||||||
|
// callers to Results (both will modify q.resultSize)
|
||||||
func (q *queue) Results(block bool) []*fetchResult {
|
func (q *queue) Results(block bool) []*fetchResult {
|
||||||
|
|
||||||
// abort early if there are no items and non-blocking requested
|
// abort early if there are no items and non-blocking requested
|
||||||
if !q.resultCache.HasCompletedItems() && !block {
|
if !q.resultCache.HasCompletedItems() && !block {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
q.lock.Lock()
|
|
||||||
defer q.lock.Unlock()
|
|
||||||
|
|
||||||
results := q.resultCache.GetCompleted(maxResultsProcess)
|
results := q.resultCache.GetCompleted(maxResultsProcess)
|
||||||
for len(results) == 0 && !q.closed {
|
for len(results) == 0 && !q.closed {
|
||||||
if !block {
|
if !block {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
q.lock.Lock()
|
||||||
q.active.Wait()
|
q.active.Wait()
|
||||||
|
q.lock.Unlock()
|
||||||
results = q.resultCache.GetCompleted(maxResultsProcess)
|
results = q.resultCache.GetCompleted(maxResultsProcess)
|
||||||
}
|
}
|
||||||
// Mark results as done
|
|
||||||
for _, result := range results {
|
for _, result := range results {
|
||||||
hash := result.Header.Hash()
|
|
||||||
delete(q.blockDonePool, hash)
|
|
||||||
delete(q.receiptDonePool, hash)
|
|
||||||
}
|
|
||||||
// Recalculate the result item weights to prevent memory exhaustion
|
// Recalculate the result item weights to prevent memory exhaustion
|
||||||
for _, result := range results {
|
|
||||||
size := result.Header.Size()
|
size := result.Header.Size()
|
||||||
for _, uncle := range result.Uncles {
|
for _, uncle := range result.Uncles {
|
||||||
size += uncle.Size()
|
size += uncle.Size()
|
||||||
|
|
@ -408,14 +401,33 @@ func (q *queue) Results(block bool) []*fetchResult {
|
||||||
for _, tx := range result.Transactions {
|
for _, tx := range result.Transactions {
|
||||||
size += tx.Size()
|
size += tx.Size()
|
||||||
}
|
}
|
||||||
q.resultSize = common.StorageSize(blockCacheSizeWeight)*size + (1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
|
q.resultSize = common.StorageSize(blockCacheSizeWeight)*size +
|
||||||
|
(1-common.StorageSize(blockCacheSizeWeight))*q.resultSize
|
||||||
|
}
|
||||||
|
// Using the newly calibrated resultsize, figure out the new throttle limit
|
||||||
|
// on the result cache
|
||||||
|
throttleThreshold := uint64((common.StorageSize(blockCacheMemory) + q.resultSize - 1) / q.resultSize)
|
||||||
|
q.resultCache.SetThrottleThreshold(throttleThreshold)
|
||||||
|
// log some info at certain times
|
||||||
|
if time.Now().Second()&0xa == 0 {
|
||||||
|
info := q.Stats()
|
||||||
|
info = append(info, "throttle", throttleThreshold)
|
||||||
|
log.Info("queue stats", info...)
|
||||||
}
|
}
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
// countProcessableItems counts the processable items.
|
func (q *queue) Stats() []interface{} {
|
||||||
func (q *queue) countProcessableItems() int {
|
q.lock.RLock()
|
||||||
return q.resultCache.CountCompleted()
|
defer q.lock.RUnlock()
|
||||||
|
return q.stats()
|
||||||
|
}
|
||||||
|
func (q *queue) stats() []interface{} {
|
||||||
|
return []interface{}{
|
||||||
|
"receiptTaskQueue", q.receiptTaskQueue.Size(),
|
||||||
|
"blockTaskQueue", q.blockTaskQueue.Size(),
|
||||||
|
"est resultSize", q.resultSize,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReserveHeaders reserves a set of headers for the given peer, skipping any
|
// ReserveHeaders reserves a set of headers for the given peer, skipping any
|
||||||
|
|
@ -461,40 +473,21 @@ func (q *queue) ReserveHeaders(p *peerConnection, count int) *fetchRequest {
|
||||||
// ReserveBodies reserves a set of body fetches for the given peer, skipping any
|
// ReserveBodies reserves a set of body fetches for the given peer, skipping any
|
||||||
// previously failed downloads. Beside the next batch of needed fetches, it also
|
// previously failed downloads. Beside the next batch of needed fetches, it also
|
||||||
// returns a flag whether empty blocks were queued requiring processing.
|
// returns a flag whether empty blocks were queued requiring processing.
|
||||||
func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, error) {
|
func (q *queue) ReserveBodies(p *peerConnection, count int) (*fetchRequest, bool, bool, error) {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
request, bleh, e := q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, q.blockDonePool, 0x01)
|
return q.reserveHeaders(p, count, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, BodyType)
|
||||||
|
|
||||||
//if request != nil {
|
|
||||||
// info := []string{}
|
|
||||||
// for _, x := range request.Headers {
|
|
||||||
// info = append(info, fmt.Sprintf("%d ", x.Number))
|
|
||||||
// }
|
|
||||||
// fmt.Printf("peer %v reserved bodies: %v\n", p.id, info)
|
|
||||||
//}
|
|
||||||
return request, bleh, e
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReserveReceipts reserves a set of receipt fetches for the given peer, skipping
|
// ReserveReceipts reserves a set of receipt fetches for the given peer, skipping
|
||||||
// any previously failed downloads. Beside the next batch of needed fetches, it
|
// any previously failed downloads. Beside the next batch of needed fetches, it
|
||||||
// also returns a flag whether empty receipts were queued requiring importing.
|
// also returns a flag whether empty receipts were queued requiring importing.
|
||||||
func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, error) {
|
func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bool, bool, error) {
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
defer q.lock.Unlock()
|
defer q.lock.Unlock()
|
||||||
|
|
||||||
request, bleh, e := q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, q.receiptDonePool, 0x02)
|
return q.reserveHeaders(p, count, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, ReceiptType)
|
||||||
|
|
||||||
//if request != nil {
|
|
||||||
// info := []string{}
|
|
||||||
// for _, x := range request.Headers {
|
|
||||||
// info = append(info, fmt.Sprintf("%d ", x.Number))
|
|
||||||
// }
|
|
||||||
// fmt.Printf("peer %v reserved receipts: %v\n", p.id, info)
|
|
||||||
//}
|
|
||||||
return request, bleh, e
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// reserveHeaders reserves a set of data download operations for a given peer,
|
// reserveHeaders reserves a set of data download operations for a given peer,
|
||||||
|
|
@ -504,52 +497,68 @@ func (q *queue) ReserveReceipts(p *peerConnection, count int) (*fetchRequest, bo
|
||||||
// Note, this method expects the queue lock to be already held for writing. The
|
// Note, this method expects the queue lock to be already held for writing. The
|
||||||
// reason the lock is not obtained in here is because the parameters already need
|
// reason the lock is not obtained in here is because the parameters already need
|
||||||
// to access the queue, so they already need a lock anyway.
|
// to access the queue, so they already need a lock anyway.
|
||||||
|
// returns:
|
||||||
|
// item - the fetchRequest
|
||||||
|
// progress, bool - whether any progress was made
|
||||||
|
// throttle, bool - if the caller should throttle for a while
|
||||||
|
// error - any error that occcurred
|
||||||
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,
|
||||||
pendPool map[string]*fetchRequest, donePool map[common.Hash]struct{}, bitFlag uint8) (*fetchRequest, bool, error) {
|
pendPool map[string]*fetchRequest, typ int) (*fetchRequest, bool, bool, error) {
|
||||||
// Short circuit if the pool has been depleted, or if the peer's already
|
// Short circuit if the pool has been depleted, or if the peer's already
|
||||||
// downloading something (sanity check not to corrupt state)
|
// downloading something (sanity check not to corrupt state)
|
||||||
if taskQueue.Empty() {
|
if taskQueue.Empty() {
|
||||||
return nil, false, nil
|
return nil, false, true, nil
|
||||||
}
|
}
|
||||||
if _, ok := pendPool[p.id]; ok {
|
if _, ok := pendPool[p.id]; ok {
|
||||||
return nil, false, nil
|
return nil, false, false, nil
|
||||||
}
|
}
|
||||||
// Calculate an upper limit on the items we might fetch (i.e. throttling)
|
|
||||||
space := q.resultSlots(pendPool, donePool)
|
|
||||||
|
|
||||||
// Retrieve a batch of tasks, skipping previously failed ones
|
// Retrieve a batch of tasks, skipping previously failed ones
|
||||||
send := make([]*types.Header, 0, count)
|
send := make([]*types.Header, 0, count)
|
||||||
skip := make([]*types.Header, 0)
|
skip := make([]*types.Header, 0)
|
||||||
progress := false
|
progress := false
|
||||||
|
throttled := false
|
||||||
for proc := 0; proc < space && len(send) < count && !taskQueue.Empty(); proc++ {
|
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.
|
||||||
header := taskQueue.PopItem().(*types.Header)
|
header := taskQueue.PopItem().(*types.Header)
|
||||||
|
// we can ask the resultcache if this header is within the
|
||||||
|
// "prioritized" segment of blocks. If it is not, we need to throttle
|
||||||
|
|
||||||
hash := header.Hash()
|
hash := header.Hash()
|
||||||
stale, item, _ := q.resultCache.AddFetch(header, q.mode == FastSync)
|
stale, throttle, item, err := q.resultCache.AddFetch(header, q.mode == FastSync)
|
||||||
if stale {
|
if stale {
|
||||||
// Don't put back in the task queue, this item has already been
|
// Don't put back in the task queue, this item has already been
|
||||||
// delivered upstream
|
// delivered upstream
|
||||||
progress = true
|
progress = true
|
||||||
donePool[hash] = struct{}{}
|
|
||||||
delete(taskPool, hash)
|
delete(taskPool, hash)
|
||||||
proc--
|
proc = proc - 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if item == nil {
|
if throttle {
|
||||||
// There are no resultslots available. Put it back in the task queue
|
// There are no resultslots available. Put it back in the task queue
|
||||||
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||||
// Set progress to true -- otherwise the peer will get
|
// Set progress to true -- otherwise the peer will get
|
||||||
// penalized for not 'accepting' requests, while in fact it's
|
// penalized for not 'accepting' requests, while in fact it's
|
||||||
// because we don't have room for more results right now
|
// because we don't have room for more results right now
|
||||||
progress = true
|
progress = true
|
||||||
|
// However, if there are any left as 'skipped', we should not tell
|
||||||
|
// the caller to throttle, since we still want some other
|
||||||
|
// peer to fetch those for us
|
||||||
|
throttled = len(skip) == 0
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Any work to be done?
|
if err != nil {
|
||||||
if item.Pending&bitFlag == 0 {
|
// this most definitely should _not_ happen
|
||||||
progress = true
|
log.Warn("reserve headers error", "error", err)
|
||||||
donePool[hash] = struct{}{}
|
// There are no resultslots available. Put it back in the task queue
|
||||||
|
taskQueue.Push(header, -int64(header.Number.Uint64()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if item.Done(typ) {
|
||||||
|
// If it's a noop, we can skip this task
|
||||||
delete(taskPool, hash)
|
delete(taskPool, hash)
|
||||||
proc--
|
proc = proc - 1
|
||||||
|
progress = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Otherwise unless the peer is known not to have the data, add to the retrieve list
|
// Otherwise unless the peer is known not to have the data, add to the retrieve list
|
||||||
|
|
@ -569,7 +578,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||||
}
|
}
|
||||||
// Assemble and return the block download request
|
// Assemble and return the block download request
|
||||||
if len(send) == 0 {
|
if len(send) == 0 {
|
||||||
return nil, progress, nil
|
return nil, progress, throttled, nil
|
||||||
}
|
}
|
||||||
request := &fetchRequest{
|
request := &fetchRequest{
|
||||||
Peer: p,
|
Peer: p,
|
||||||
|
|
@ -577,7 +586,7 @@ func (q *queue) reserveHeaders(p *peerConnection, count int, taskPool map[common
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
}
|
}
|
||||||
pendPool[p.id] = request
|
pendPool[p.id] = request
|
||||||
return request, progress, nil
|
return request, progress, throttled, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CancelHeaders aborts a fetch request, returning all pending skeleton indexes to the queue.
|
// CancelHeaders aborts a fetch request, returning all pending skeleton indexes to the queue.
|
||||||
|
|
@ -807,10 +816,10 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, uncleLi
|
||||||
reconstruct := func(index int, result *fetchResult) {
|
reconstruct := func(index int, result *fetchResult) {
|
||||||
result.Transactions = txLists[index]
|
result.Transactions = txLists[index]
|
||||||
result.Uncles = uncleLists[index]
|
result.Uncles = uncleLists[index]
|
||||||
// clear body flag, AND with 1111 1110
|
result.SetBodyDone()
|
||||||
result.Pending &= 0xfe
|
|
||||||
}
|
}
|
||||||
return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool, q.blockDonePool, bodyReqTimer, len(txLists), validate, reconstruct)
|
return q.deliver(id, q.blockTaskPool, q.blockTaskQueue, q.blockPendPool,
|
||||||
|
bodyReqTimer, len(txLists), validate, reconstruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeliverReceipts injects a receipt retrieval response into the results queue.
|
// DeliverReceipts injects a receipt retrieval response into the results queue.
|
||||||
|
|
@ -826,18 +835,19 @@ func (q *queue) DeliverReceipts(id string, receiptList [][]*types.Receipt) (int,
|
||||||
}
|
}
|
||||||
reconstruct := func(index int, result *fetchResult) {
|
reconstruct := func(index int, result *fetchResult) {
|
||||||
result.Receipts = receiptList[index]
|
result.Receipts = receiptList[index]
|
||||||
// clear bit 1, AND with 1111 1101
|
result.SetReceiptsDone()
|
||||||
result.Pending &= 0xfd
|
|
||||||
}
|
}
|
||||||
return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool, q.receiptDonePool, receiptReqTimer, len(receiptList), validate, reconstruct)
|
return q.deliver(id, q.receiptTaskPool, q.receiptTaskQueue, q.receiptPendPool,
|
||||||
|
receiptReqTimer, len(receiptList), validate, reconstruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deliver injects a data retrieval response into the results queue.
|
// deliver injects a data retrieval response into the results queue.
|
||||||
//
|
//
|
||||||
// This method obtains the lock as needed
|
// This method obtains the lock as needed
|
||||||
func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, taskQueue *prque.Prque,
|
func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header,
|
||||||
pendPool map[string]*fetchRequest, donePool map[common.Hash]struct{}, reqTimer metrics.Timer,
|
taskQueue *prque.Prque, pendPool map[string]*fetchRequest, reqTimer metrics.Timer,
|
||||||
results int, validate func(index int, header *types.Header) error, reconstruct func(index int, result *fetchResult)) (int, error) {
|
results int, validate func(index int, header *types.Header) error,
|
||||||
|
reconstruct func(index int, result *fetchResult)) (int, error) {
|
||||||
|
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
// Short circuit if the data was never requested
|
// Short circuit if the data was never requested
|
||||||
|
|
@ -867,37 +877,27 @@ func (q *queue) deliver(id string, taskPool map[common.Hash]*types.Header, taskQ
|
||||||
if i >= results {
|
if i >= results {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
header.Hash()
|
||||||
// Validate the fields
|
// Validate the fields
|
||||||
if err := validate(i, header); err != nil {
|
if err := validate(i, header); err != nil {
|
||||||
failure = err
|
failure = err
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
header.Hash()
|
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
q.lock.Lock()
|
q.lock.Lock()
|
||||||
var acceptCount = 0
|
var acceptCount = 0
|
||||||
for _, header := range request.Headers[:i] {
|
for _, header := range request.Headers[:i] {
|
||||||
// TODO @holiman
|
|
||||||
// q.resultCache.deliver(header, data, reconstruct)
|
|
||||||
// or
|
|
||||||
// q.resultCache.deliverBody , q.resultCache.deliverReceipts
|
|
||||||
// or
|
|
||||||
// q.resultCache.deliverBodies( bodies, headers[0])
|
|
||||||
|
|
||||||
if res, stale, err := q.resultCache.GetFetchResult(header); err == nil {
|
if res, stale, err := q.resultCache.GetFetchResult(header); err == nil {
|
||||||
reconstruct(acceptCount, res)
|
reconstruct(acceptCount, res)
|
||||||
} else {
|
} else {
|
||||||
// else: betweeen here and above, some other peer filled this result
|
// else: betweeen here and above, some other peer filled this result,
|
||||||
// we just ignore and move on
|
// or it was indeed a no-op. This should not happen, but if it does it's
|
||||||
// TODO @holiman
|
// not something to panic about
|
||||||
// figure out how this can happen (it shouldn't)
|
|
||||||
log.Info("delivery stale?", "err", err, "stale", stale)
|
log.Info("delivery stale?", "err", err, "stale", stale)
|
||||||
failure = errStaleDelivery
|
failure = errStaleDelivery
|
||||||
}
|
}
|
||||||
hash := header.Hash()
|
delete(taskPool, header.Hash())
|
||||||
donePool[hash] = struct{}{}
|
|
||||||
delete(taskPool, hash)
|
|
||||||
// Clean up a successful fetch
|
// Clean up a successful fetch
|
||||||
request.Headers[acceptCount] = nil
|
request.Headers[acceptCount] = nil
|
||||||
acceptCount++
|
acceptCount++
|
||||||
|
|
|
||||||
|
|
@ -21,126 +21,129 @@ package downloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resultStore struct {
|
type resultStore struct {
|
||||||
items []*fetchResult // Downloaded but not yet delivered fetch results
|
items []*fetchResult // Downloaded but not yet delivered fetch results
|
||||||
lock *sync.RWMutex // lock protect internals
|
lock *sync.RWMutex // lock protect internals
|
||||||
resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
resultOffset uint64 // Offset of the first cached fetch result in the block chain
|
||||||
resultSize common.StorageSize // Approximate size of a block (exponential moving average)
|
|
||||||
|
|
||||||
// Internal index of first non-completed entry, updated atomically when needed.
|
// Internal index of first non-completed entry, updated atomically when needed.
|
||||||
// If all items are complete, this will equal length(items), so
|
// If all items are complete, this will equal length(items), so
|
||||||
// *important* : is not safe to use for indexing without checking against length
|
// *important* : is not safe to use for indexing without checking against length
|
||||||
indexIncomplete int32
|
indexIncomplete int32 // atomic access
|
||||||
|
|
||||||
|
// throttleThreshold is the limit up to which we _want_ to fill the
|
||||||
|
// results. If blocks are large, we want to limit the results to less
|
||||||
|
// than the number of available slots, and maybe only fill 1024 out of
|
||||||
|
// 8192 possible places. The queue will, at certain times, recalibrate
|
||||||
|
// this index.
|
||||||
|
throttleThreshold uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newResultStore(size int) *resultStore {
|
func newResultStore(size int) *resultStore {
|
||||||
return &resultStore{
|
return &resultStore{
|
||||||
resultOffset: 0,
|
resultOffset: 0,
|
||||||
items: make([]*fetchResult, size),
|
items: make([]*fetchResult, size),
|
||||||
resultSize: 0, // TODO: use a saner default, left at zero as it was legacy
|
|
||||||
lock: new(sync.RWMutex),
|
lock: new(sync.RWMutex),
|
||||||
|
throttleThreshold: 3 * uint64(size) / 4, // 75%
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddFetch adds a header for body/receipt fetching.
|
func (r *resultStore) SetThrottleThreshold(threshold uint64) {
|
||||||
// returning
|
r.lock.Lock()
|
||||||
// stale -- if true, this item is already passed, and should not be requested again
|
defer r.lock.Unlock()
|
||||||
// fetchResult -- if `nil`, that means no fetch was created, and that the
|
limit := uint64(len(r.items)) * 3 / 4
|
||||||
// if an error is returned, that most likely means backpressure prevents the results from expanding,
|
if threshold >= limit {
|
||||||
// and someone needs to take care of results
|
threshold = limit
|
||||||
func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale bool, item *fetchResult, err error) {
|
}
|
||||||
hash := header.Hash()
|
r.throttleThreshold = threshold
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFetch adds a header for body/receipt fetching. This is used when the queue
|
||||||
|
// wants to reserve headers for fetching.
|
||||||
|
// It returns the following:
|
||||||
|
// stale -- if true, this item is already passed, and should not be requested again.
|
||||||
|
// throttled -- if true, the resultcache is at capacity, and this particular header is not
|
||||||
|
// prio right now
|
||||||
|
// fetchResult -- the result to store data into
|
||||||
|
// err -- any error that occurred
|
||||||
|
func (r *resultStore) AddFetch(header *types.Header, fastSync bool) (stale, throttled bool, item *fetchResult, err error) {
|
||||||
|
header.Hash()
|
||||||
r.lock.RLock()
|
r.lock.RLock()
|
||||||
item, _, stale, err = r.getFetchResult(header)
|
var index int
|
||||||
if err != nil {
|
if item, index, stale, throttled, err = r.getFetchResult(header); err != nil {
|
||||||
r.lock.RUnlock()
|
r.lock.RUnlock()
|
||||||
log.Info("resultcache addfetch err [1]", "error", err.Error())
|
return
|
||||||
// can't create a fetchResult right away
|
}
|
||||||
return stale, nil, err
|
if stale {
|
||||||
|
r.lock.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if throttled {
|
||||||
|
// Index is above the current threshold of 'prioritized' blocks,
|
||||||
|
log.Debug("resultcache throttle", "index", index, "threshold", r.throttleThreshold)
|
||||||
|
r.lock.RUnlock()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if item != nil {
|
if item != nil {
|
||||||
|
// All good, item already exists (perhaps a receipt fetch following
|
||||||
|
// a body fetch)
|
||||||
r.lock.RUnlock()
|
r.lock.RUnlock()
|
||||||
return false, item, nil
|
return
|
||||||
}
|
}
|
||||||
r.lock.RUnlock()
|
r.lock.RUnlock()
|
||||||
// Need to create a fetchresult, and as we've just release the Rlock,
|
// Need to create a fetchresult, and as we've just release the Rlock,
|
||||||
// we need to check again after obtaining the writelock
|
// we need to check again after obtaining the writelock
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
var index int
|
// Same checks as above, now with wlock
|
||||||
item, index, stale, err = r.getFetchResult(header)
|
if item, index, stale, throttled, err = r.getFetchResult(header); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
// can't create a fetchResult right away
|
}
|
||||||
log.Info("resultcache addfetch err [2]", "error", err.Error())
|
if stale || throttled {
|
||||||
return stale, nil, err
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
if item == nil {
|
if item == nil {
|
||||||
item = &fetchResult{
|
item = newFetchResult(header, fastSync)
|
||||||
Hash: hash,
|
|
||||||
Header: header,
|
|
||||||
}
|
|
||||||
// Need to fetch body?
|
|
||||||
if !header.EmptyBody() {
|
|
||||||
// yes
|
|
||||||
item.Pending |= 0x1
|
|
||||||
}
|
|
||||||
// Do we need to fetch receipts?
|
|
||||||
if fastSync && !header.EmptyReceipts() {
|
|
||||||
item.Pending |= 0x2
|
|
||||||
}
|
|
||||||
r.items[index] = item
|
r.items[index] = item
|
||||||
}
|
}
|
||||||
return false, item, nil
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFetchResult returns the fetchResult for the given header. If the 'stale' flag
|
||||||
|
// is true, that means the header has already been delivered 'upstream'.
|
||||||
func (r *resultStore) GetFetchResult(header *types.Header) (*fetchResult, bool, error) {
|
func (r *resultStore) GetFetchResult(header *types.Header) (*fetchResult, bool, error) {
|
||||||
r.lock.RLock()
|
r.lock.RLock()
|
||||||
defer r.lock.RUnlock()
|
defer r.lock.RUnlock()
|
||||||
res, _, stale, err := r.getFetchResult(header)
|
res, _, stale, _, err := r.getFetchResult(header)
|
||||||
return res, stale, err
|
return res, stale, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFetchResult returns the fetchResult corresponding to the given item, and the index where
|
// getFetchResult returns the fetchResult corresponding to the given item, and the index where
|
||||||
// the result is stored.
|
// the result is stored.
|
||||||
// There are two ways it can error:
|
func (r *resultStore) getFetchResult(header *types.Header) (item *fetchResult, index int, stale, throttle bool, err error) {
|
||||||
// 1. The header is too far off in the future, and we don't have room for it.
|
|
||||||
// 2. The header is stale, and the results for that header has already been delivered upstream.
|
|
||||||
func (r *resultStore) getFetchResult(header *types.Header) (item *fetchResult, index int, stale bool, err error) {
|
|
||||||
index = int(header.Number.Int64() - int64(r.resultOffset))
|
index = int(header.Number.Int64() - int64(r.resultOffset))
|
||||||
|
throttle = index >= int(r.throttleThreshold)
|
||||||
|
stale = index < 0
|
||||||
|
|
||||||
if index >= len(r.items) {
|
if index >= len(r.items) {
|
||||||
err = fmt.Errorf("index allocation went beyond available resultStore space "+
|
err = fmt.Errorf("index allocation went beyond available resultStore space "+
|
||||||
"(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d",
|
"(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d",
|
||||||
index, header.Number.Int64(), r.resultOffset, len(r.items))
|
index, header.Number.Int64(), r.resultOffset, len(r.items))
|
||||||
return item, index, stale, err
|
return
|
||||||
}
|
}
|
||||||
if index < 0 {
|
if stale {
|
||||||
stale = true
|
return
|
||||||
err = fmt.Errorf("index allocation went beyond available resultStore space "+
|
|
||||||
"(index [%d] = header [%d] - resultOffset [%d], len(resultStore) = %d",
|
|
||||||
index, header.Number.Int64(), r.resultOffset, len(r.items))
|
|
||||||
return item, index, stale, err
|
|
||||||
}
|
}
|
||||||
item = r.items[index]
|
item = r.items[index]
|
||||||
return item, index, stale, err
|
return
|
||||||
}
|
|
||||||
|
|
||||||
// numberSpan returns the header number start and end, for the headers
|
|
||||||
// currently "allocated" for download (both completed, in-flight and pending)
|
|
||||||
func (r *resultStore) NumberSpan() (uint64, uint64) {
|
|
||||||
r.lock.RLock()
|
|
||||||
defer r.lock.RUnlock()
|
|
||||||
return r.resultOffset, r.resultOffset + uint64(len(r.items))
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hasCompletedItems returns true if there are processable items available
|
// hasCompletedItems returns true if there are processable items available
|
||||||
|
|
@ -151,81 +154,33 @@ func (r *resultStore) HasCompletedItems() bool {
|
||||||
if len(r.items) == 0 {
|
if len(r.items) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if item := r.items[0]; item != nil && item.Pending == 0 {
|
if item := r.items[0]; item != nil && item.AllDone() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// CountCompleted returns the number of items completed
|
// countCompleted returns the number of items ready for delivery, stopping at
|
||||||
func (r *resultStore) CountCompleted() int {
|
// the first non-complete item.
|
||||||
r.lock.RLock()
|
// It assumes (at least) rlock is held
|
||||||
defer r.lock.RUnlock()
|
|
||||||
return r.countCompleted()
|
|
||||||
}
|
|
||||||
|
|
||||||
// countCompleted returns the number of items completed
|
|
||||||
// assumes (at least) rlock is held
|
|
||||||
func (r *resultStore) countCompleted() int {
|
func (r *resultStore) countCompleted() int {
|
||||||
// We iterate from the already known complete point, and see
|
// We iterate from the already known complete point, and see
|
||||||
// if any more has completed since last count
|
// if any more has completed since last count
|
||||||
// debug
|
|
||||||
/*
|
|
||||||
var (
|
|
||||||
nils = 0
|
|
||||||
fins = 0
|
|
||||||
bodyneeds = 0
|
|
||||||
receiptneeds = 0
|
|
||||||
)
|
|
||||||
var ctx []interface{}
|
|
||||||
for _, item := range r.items {
|
|
||||||
if item == nil {
|
|
||||||
nils++
|
|
||||||
} else {
|
|
||||||
if item.Pending == 0 {
|
|
||||||
fins++
|
|
||||||
} else {
|
|
||||||
if item.Pending&0x01 != 0 {
|
|
||||||
bodyneeds++
|
|
||||||
} else {
|
|
||||||
receiptneeds++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx = append(ctx, "items", len(r.items), "nils", nil, "fins", fins,
|
|
||||||
"needB", bodyneeds, "needR", receiptneeds)
|
|
||||||
*/
|
|
||||||
/// end debug
|
|
||||||
index := atomic.LoadInt32(&r.indexIncomplete)
|
index := atomic.LoadInt32(&r.indexIncomplete)
|
||||||
for ; ; index++ {
|
for ; ; index++ {
|
||||||
if index >= int32(len(r.items)) {
|
if index >= int32(len(r.items)) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
result := r.items[index]
|
result := r.items[index]
|
||||||
if result == nil || result.Pending > 0 {
|
if result == nil || !result.AllDone() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
if index < int32(len(r.items)) {
|
|
||||||
//ctx = append(ctx, []interface{}{"index", index, "blocknum", uint64(index) + r.resultOffset}...)
|
|
||||||
if r.items[index] != nil {
|
|
||||||
log.Info("resultstore", ctx...)
|
|
||||||
} else {
|
|
||||||
ctx = append(ctx, []interface{}{"first missing", "nil"}...)
|
|
||||||
log.Info("resultstore", ctx...)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ctx = append(ctx, []interface{}{"first missing", "out of range"}...)
|
|
||||||
log.Info("resultstore", ctx...)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
atomic.StoreInt32(&r.indexIncomplete, index)
|
atomic.StoreInt32(&r.indexIncomplete, index)
|
||||||
return int(index)
|
return int(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCompleted returns the next batch of completed fetchresults
|
// GetCompleted returns the next batch of completed fetchResults
|
||||||
func (r *resultStore) GetCompleted(limit int) []*fetchResult {
|
func (r *resultStore) GetCompleted(limit int) []*fetchResult {
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
|
|
@ -244,11 +199,12 @@ func (r *resultStore) GetCompleted(limit int) []*fetchResult {
|
||||||
}
|
}
|
||||||
// Advance the expected block number of the first cache entry.
|
// Advance the expected block number of the first cache entry.
|
||||||
r.resultOffset += uint64(limit)
|
r.resultOffset += uint64(limit)
|
||||||
// And subtract the number of items from our two indexes
|
// And subtract the number of items from our index
|
||||||
atomic.StoreInt32(&r.indexIncomplete, int32(completed-limit))
|
atomic.AddInt32(&r.indexIncomplete, int32(-limit))
|
||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prepare initialises the offset with the given block number
|
||||||
func (r *resultStore) Prepare(offset uint64) {
|
func (r *resultStore) Prepare(offset uint64) {
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
if r.resultOffset < offset {
|
if r.resultOffset < offset {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue