mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
downloader: more accurate deliverytime calculation, less mem overhead in state requests
This commit is contained in:
parent
1fe952ed44
commit
e121488447
3 changed files with 49 additions and 36 deletions
|
|
@ -1097,7 +1097,9 @@ func (d *Downloader) fillHeaderSkeleton(from uint64, skeleton []*types.Header) (
|
|||
}
|
||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchHeaders(req.From, MaxHeaderFetch) }
|
||||
capacity = func(p *peerConnection) int { return p.HeaderCapacity(d.requestRTT()) }
|
||||
setIdle = func(p *peerConnection, accepted int) { p.SetHeadersIdle(accepted) }
|
||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) {
|
||||
p.SetHeadersIdle(accepted, deliveryTime)
|
||||
}
|
||||
)
|
||||
err := d.fetchParts(d.headerCh, deliver, d.queue.headerContCh, expire,
|
||||
d.queue.PendingHeaders, d.queue.InFlightHeaders, throttle, reserve,
|
||||
|
|
@ -1120,10 +1122,10 @@ func (d *Downloader) fetchBodies(from uint64) error {
|
|||
pack := packet.(*bodyPack)
|
||||
return d.queue.DeliverBodies(pack.peerID, pack.transactions, pack.uncles)
|
||||
}
|
||||
expire = func() map[string]int {return d.queue.ExpireBodies(d.requestTTL())}
|
||||
expire = func() map[string]int { return d.queue.ExpireBodies(d.requestTTL()) }
|
||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchBodies(req) }
|
||||
capacity = func(p *peerConnection) int { return p.BlockCapacity(d.requestRTT()) }
|
||||
setIdle = func(p *peerConnection, accepted int) { p.SetBodiesIdle(accepted) }
|
||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) { p.SetBodiesIdle(accepted, deliveryTime) }
|
||||
)
|
||||
err := d.fetchParts(d.bodyCh, deliver, d.bodyWakeCh, expire,
|
||||
d.queue.PendingBlocks, d.queue.InFlightBlocks, d.queue.ShouldThrottleBlocks, d.queue.ReserveBodies,
|
||||
|
|
@ -1147,7 +1149,9 @@ func (d *Downloader) fetchReceipts(from uint64) error {
|
|||
expire = func() map[string]int { return d.queue.ExpireReceipts(d.requestTTL()) }
|
||||
fetch = func(p *peerConnection, req *fetchRequest) error { return p.FetchReceipts(req) }
|
||||
capacity = func(p *peerConnection) int { return p.ReceiptCapacity(d.requestRTT()) }
|
||||
setIdle = func(p *peerConnection, accepted int) { p.SetReceiptsIdle(accepted) }
|
||||
setIdle = func(p *peerConnection, accepted int, deliveryTime time.Time) {
|
||||
p.SetReceiptsIdle(accepted, deliveryTime)
|
||||
}
|
||||
)
|
||||
err := d.fetchParts(d.receiptCh, deliver, d.receiptWakeCh, expire,
|
||||
d.queue.PendingReceipts, d.queue.InFlightReceipts, d.queue.ShouldThrottleReceipts, d.queue.ReserveReceipts,
|
||||
|
|
@ -1185,7 +1189,7 @@ func (d *Downloader) fetchReceipts(from uint64) error {
|
|||
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),
|
||||
fetchHook func([]*types.Header), fetch func(*peerConnection, *fetchRequest) error, cancel func(*fetchRequest), capacity func(*peerConnection) int,
|
||||
idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int), kind string) error {
|
||||
idle func() ([]*peerConnection, int), setIdle func(*peerConnection, int, time.Time), kind string) error {
|
||||
|
||||
// Create a ticker to detect expired retrieval tasks
|
||||
ticker := time.NewTicker(200 * time.Millisecond)
|
||||
|
|
@ -1201,6 +1205,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
|
|||
return errCanceled
|
||||
|
||||
case packet := <-deliveryCh:
|
||||
deliveryTime := time.Now()
|
||||
// If the peer was previously banned and failed to deliver its pack
|
||||
// in a reasonable time frame, ignore its message.
|
||||
if peer := d.peers.Peer(packet.PeerId()); peer != nil {
|
||||
|
|
@ -1213,7 +1218,7 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
|
|||
// caused by a timed out request which came through in the end), set it to
|
||||
// idle. If the delivery's stale, the peer should have already been idled.
|
||||
if err != errStaleDelivery {
|
||||
setIdle(peer, accepted)
|
||||
setIdle(peer, accepted, deliveryTime)
|
||||
}
|
||||
// Issue a log to the user to see what's going on
|
||||
switch {
|
||||
|
|
@ -1264,9 +1269,9 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
|
|||
// The reason the minimum threshold is 2 is because the downloader tries to estimate the bandwidth
|
||||
// and latency of a peer separately, which requires pushing the measures capacity a bit and seeing
|
||||
// how response times reacts, to it always requests one more than the minimum (i.e. min 2).
|
||||
if fails > 8 {
|
||||
if fails > 2 {
|
||||
peer.log.Trace("Data delivery timed out", "type", kind)
|
||||
setIdle(peer, 0)
|
||||
setIdle(peer, 0, time.Now())
|
||||
} else {
|
||||
peer.log.Debug("Stalling delivery, dropping", "type", kind)
|
||||
|
||||
|
|
@ -1323,8 +1328,11 @@ func (d *Downloader) fetchParts(deliveryCh chan dataPack, deliver func(dataPack)
|
|||
progressed = true
|
||||
}
|
||||
if request == nil {
|
||||
//peer.log.Info("no request allocated this loop", "type", kind)
|
||||
continue
|
||||
// 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
|
||||
break
|
||||
}
|
||||
if request.From > 0 {
|
||||
peer.log.Trace("Requesting new batch of data", "type", kind, "from", request.From)
|
||||
|
|
|
|||
|
|
@ -227,34 +227,34 @@ func (p *peerConnection) FetchNodeData(hashes []common.Hash) error {
|
|||
// SetHeadersIdle sets the peer to idle, allowing it to execute new header retrieval
|
||||
// requests. Its estimated header retrieval throughput is updated with that measured
|
||||
// just now.
|
||||
func (p *peerConnection) SetHeadersIdle(delivered int) {
|
||||
p.setIdle(p.headerStarted, delivered, &p.headerThroughput, &p.headerIdle)
|
||||
func (p *peerConnection) SetHeadersIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.headerStarted), delivered, &p.headerThroughput, &p.headerIdle)
|
||||
}
|
||||
|
||||
// SetBodiesIdle sets the peer to idle, allowing it to execute block body retrieval
|
||||
// requests. Its estimated body retrieval throughput is updated with that measured
|
||||
// just now.
|
||||
func (p *peerConnection) SetBodiesIdle(delivered int) {
|
||||
p.setIdle(p.blockStarted, delivered, &p.blockThroughput, &p.blockIdle)
|
||||
func (p *peerConnection) SetBodiesIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.blockStarted), delivered, &p.blockThroughput, &p.blockIdle)
|
||||
}
|
||||
|
||||
// SetReceiptsIdle sets the peer to idle, allowing it to execute new receipt
|
||||
// retrieval requests. Its estimated receipt retrieval throughput is updated
|
||||
// with that measured just now.
|
||||
func (p *peerConnection) SetReceiptsIdle(delivered int) {
|
||||
p.setIdle(p.receiptStarted, delivered, &p.receiptThroughput, &p.receiptIdle)
|
||||
func (p *peerConnection) SetReceiptsIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.receiptStarted), delivered, &p.receiptThroughput, &p.receiptIdle)
|
||||
}
|
||||
|
||||
// SetNodeDataIdle sets the peer to idle, allowing it to execute new state trie
|
||||
// data retrieval requests. Its estimated state retrieval throughput is updated
|
||||
// with that measured just now.
|
||||
func (p *peerConnection) SetNodeDataIdle(delivered int) {
|
||||
p.setIdle(p.stateStarted, delivered, &p.stateThroughput, &p.stateIdle)
|
||||
func (p *peerConnection) SetNodeDataIdle(delivered int, deliveryTime time.Time) {
|
||||
p.setIdle(deliveryTime.Sub(p.stateStarted), delivered, &p.stateThroughput, &p.stateIdle)
|
||||
}
|
||||
|
||||
// setIdle sets the peer to idle, allowing it to execute new retrieval requests.
|
||||
// Its estimated retrieval throughput is updated with that measured just now.
|
||||
func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *float64, idle *int32) {
|
||||
func (p *peerConnection) setIdle(elapsed time.Duration, delivered int, throughput *float64, idle *int32) {
|
||||
// Irrelevant of the scaling, make sure the peer ends up idle
|
||||
defer atomic.StoreInt32(idle, 0)
|
||||
|
||||
|
|
@ -267,7 +267,9 @@ func (p *peerConnection) setIdle(started time.Time, delivered int, throughput *f
|
|||
return
|
||||
}
|
||||
// Otherwise update the throughput with a new measurement
|
||||
elapsed := time.Since(started) + 1 // +1 (ns) to ensure non-zero divisor
|
||||
if elapsed <= 0{
|
||||
elapsed = 1 // +1 (ns) to ensure non-zero divisor
|
||||
}
|
||||
measured := float64(delivered) / (float64(elapsed) / float64(time.Second))
|
||||
|
||||
*throughput = (1-measurementImpact)*(*throughput) + measurementImpact*measured
|
||||
|
|
@ -285,7 +287,7 @@ func (p *peerConnection) HeaderCapacity(targetRTT time.Duration) int {
|
|||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(7+math.Max(1, p.headerThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxHeaderFetch)))
|
||||
return int(math.Min(1+math.Max(1, p.headerThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxHeaderFetch)))
|
||||
}
|
||||
|
||||
// BlockCapacity retrieves the peers block download allowance based on its
|
||||
|
|
@ -294,7 +296,7 @@ func (p *peerConnection) BlockCapacity(targetRTT time.Duration) int {
|
|||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(7+math.Max(1, p.blockThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxBlockFetch)))
|
||||
return int(math.Min(1+math.Max(1, p.blockThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxBlockFetch)))
|
||||
}
|
||||
|
||||
// ReceiptCapacity retrieves the peers receipt download allowance based on its
|
||||
|
|
@ -303,7 +305,7 @@ func (p *peerConnection) ReceiptCapacity(targetRTT time.Duration) int {
|
|||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(7+math.Max(1, p.receiptThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxReceiptFetch)))
|
||||
return int(math.Min(1+math.Max(1, p.receiptThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxReceiptFetch)))
|
||||
}
|
||||
|
||||
// NodeDataCapacity retrieves the peers state download allowance based on its
|
||||
|
|
@ -312,7 +314,7 @@ func (p *peerConnection) NodeDataCapacity(targetRTT time.Duration) int {
|
|||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return int(math.Min(7+math.Max(1, p.stateThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxStateFetch)))
|
||||
return int(math.Min(1+math.Max(1, p.stateThroughput*float64(targetRTT)/float64(time.Second)), float64(MaxStateFetch)))
|
||||
}
|
||||
|
||||
// MarkLacking appends a new entity to the set of items (blocks, receipts, states)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import (
|
|||
// stateReq represents a batch of state fetch requests grouped together into
|
||||
// a single data retrieval network packet.
|
||||
type stateReq struct {
|
||||
items []common.Hash // Hashes of the state items to download
|
||||
nItems uint16 // Number of items requested for download (max is 384, so uint16 is sufficient)
|
||||
tasks map[common.Hash]*stateTask // Download tasks to track previous attempts
|
||||
timeout time.Duration // Maximum round trip time for this to complete
|
||||
timer *time.Timer // Timer to fire when the RTT timeout expires
|
||||
|
|
@ -100,7 +100,7 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
|||
// available for the next sync.
|
||||
for _, req := range active {
|
||||
req.timer.Stop()
|
||||
req.peer.SetNodeDataIdle(len(req.items))
|
||||
req.peer.SetNodeDataIdle(int(req.nItems), time.Now())
|
||||
}
|
||||
}()
|
||||
// Run the state sync.
|
||||
|
|
@ -305,9 +305,10 @@ func (s *stateSync) loop() (err error) {
|
|||
return errCanceled
|
||||
|
||||
case req := <-s.deliver:
|
||||
deliveryTime := time.Now()
|
||||
// Response, disconnect or timeout triggered, drop the peer if stalling
|
||||
log.Trace("Received node data response", "peer", req.peer.id, "count", len(req.response), "dropped", req.dropped, "timeout", !req.dropped && req.timedOut())
|
||||
if len(req.items) <= 2 && !req.dropped && req.timedOut() {
|
||||
if req.nItems <= 2 && !req.dropped && req.timedOut() {
|
||||
// 2 items are the minimum requested, if even that times out, we've no use of
|
||||
// this peer at the moment.
|
||||
log.Warn("Stalling state sync, dropping peer", "peer", req.peer.id)
|
||||
|
|
@ -335,7 +336,7 @@ func (s *stateSync) loop() (err error) {
|
|||
log.Warn("Node data write error", "err", err)
|
||||
return err
|
||||
}
|
||||
req.peer.SetNodeDataIdle(delivered)
|
||||
req.peer.SetNodeDataIdle(delivered, deliveryTime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
|
@ -368,14 +369,14 @@ func (s *stateSync) assignTasks() {
|
|||
// Assign a batch of fetches proportional to the estimated latency/bandwidth
|
||||
cap := p.NodeDataCapacity(s.d.requestRTT())
|
||||
req := &stateReq{peer: p, timeout: s.d.requestTTL()}
|
||||
s.fillTasks(cap, req)
|
||||
items := s.fillTasks(cap, req)
|
||||
|
||||
// If the peer was assigned tasks to fetch, send the network request
|
||||
if len(req.items) > 0 {
|
||||
req.peer.log.Trace("Requesting new batch of data", "type", "state", "count", len(req.items))
|
||||
if len(items) > 0 {
|
||||
req.peer.log.Trace("Requesting new batch of data", "type", "state", "count", len(items))
|
||||
select {
|
||||
case s.d.trackStateReq <- req:
|
||||
req.peer.FetchNodeData(req.items)
|
||||
req.peer.FetchNodeData(items)
|
||||
case <-s.cancel:
|
||||
case <-s.d.cancelCh:
|
||||
}
|
||||
|
|
@ -385,7 +386,7 @@ func (s *stateSync) assignTasks() {
|
|||
|
||||
// fillTasks fills the given request object with a maximum of n state download
|
||||
// tasks to send to the remote peer.
|
||||
func (s *stateSync) fillTasks(n int, req *stateReq) {
|
||||
func (s *stateSync) fillTasks(n int, req *stateReq) []common.Hash {
|
||||
// Refill available tasks from the scheduler.
|
||||
if len(s.tasks) < n {
|
||||
new := s.sched.Missing(n - len(s.tasks))
|
||||
|
|
@ -394,11 +395,11 @@ func (s *stateSync) fillTasks(n int, req *stateReq) {
|
|||
}
|
||||
}
|
||||
// Find tasks that haven't been tried with the request's peer.
|
||||
req.items = make([]common.Hash, 0, n)
|
||||
items := make([]common.Hash, 0, n)
|
||||
req.tasks = make(map[common.Hash]*stateTask, n)
|
||||
for hash, t := range s.tasks {
|
||||
// Stop when we've gathered enough requests
|
||||
if len(req.items) == n {
|
||||
if len(items) == n {
|
||||
break
|
||||
}
|
||||
// Skip any requests we've already tried from this peer
|
||||
|
|
@ -407,10 +408,12 @@ func (s *stateSync) fillTasks(n int, req *stateReq) {
|
|||
}
|
||||
// Assign the request to this peer
|
||||
t.attempts[req.peer.id] = struct{}{}
|
||||
req.items = append(req.items, hash)
|
||||
items = append(items, hash)
|
||||
req.tasks[hash] = t
|
||||
delete(s.tasks, hash)
|
||||
}
|
||||
req.nItems = uint16(len(items))
|
||||
return items
|
||||
}
|
||||
|
||||
// process iterates over a batch of delivered state data, injecting each item
|
||||
|
|
|
|||
Loading…
Reference in a new issue