|
|
@@ -63,6 +63,10 @@ func (d *Downloader) syncState(root common.Hash) *stateSync {
|
|
|
s := newStateSync(d, root)
|
|
|
select {
|
|
|
case d.stateSyncStart <- s:
|
|
|
+ // If we tell the statesync to restart with a new root, we also need
|
|
|
+ // to wait for it to actually also start -- when old requests have timed
|
|
|
+ // out or been delivered
|
|
|
+ <-s.started
|
|
|
case <-d.quitCh:
|
|
|
s.err = errCancelStateFetch
|
|
|
close(s.done)
|
|
|
@@ -95,15 +99,9 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
|
|
finished []*stateReq // Completed or failed requests
|
|
|
timeout = make(chan *stateReq) // Timed out active requests
|
|
|
)
|
|
|
- defer func() {
|
|
|
- // Cancel active request timers on exit. Also set peers to idle so they're
|
|
|
- // available for the next sync.
|
|
|
- for _, req := range active {
|
|
|
- req.timer.Stop()
|
|
|
- req.peer.SetNodeDataIdle(len(req.items))
|
|
|
- }
|
|
|
- }()
|
|
|
+
|
|
|
// Run the state sync.
|
|
|
+ log.Trace("State sync starting", "root", s.root)
|
|
|
go s.run()
|
|
|
defer s.Cancel()
|
|
|
|
|
|
@@ -126,9 +124,11 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
|
|
select {
|
|
|
// The stateSync lifecycle:
|
|
|
case next := <-d.stateSyncStart:
|
|
|
+ d.spindownStateSync(active, finished, timeout, peerDrop)
|
|
|
return next
|
|
|
|
|
|
case <-s.done:
|
|
|
+ d.spindownStateSync(active, finished, timeout, peerDrop)
|
|
|
return nil
|
|
|
|
|
|
// Send the next finished request to the current sync:
|
|
|
@@ -189,11 +189,9 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
|
|
// causes valid requests to go missing and sync to get stuck.
|
|
|
if old := active[req.peer.id]; old != nil {
|
|
|
log.Warn("Busy peer assigned new state fetch", "peer", old.peer.id)
|
|
|
-
|
|
|
- // Make sure the previous one doesn't get siletly lost
|
|
|
+ // Move the previous request to the finished set
|
|
|
old.timer.Stop()
|
|
|
old.dropped = true
|
|
|
-
|
|
|
finished = append(finished, old)
|
|
|
}
|
|
|
// Start a timer to notify the sync loop if the peer stalled.
|
|
|
@@ -210,6 +208,46 @@ func (d *Downloader) runStateSync(s *stateSync) *stateSync {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// spindownStateSync 'drains' the outstanding requests; some will be delivered and other
|
|
|
+// will time out. This is to ensure that when the next stateSync starts working, all peers
|
|
|
+// are marked as idle and de facto _are_ idle.
|
|
|
+func (d *Downloader) spindownStateSync(active map[string]*stateReq, finished []*stateReq, timeout chan *stateReq, peerDrop chan *peerConnection) {
|
|
|
+ log.Trace("State sync spinning down", "active", len(active), "finished", len(finished))
|
|
|
+
|
|
|
+ for len(active) > 0 {
|
|
|
+ var (
|
|
|
+ req *stateReq
|
|
|
+ reason string
|
|
|
+ )
|
|
|
+ select {
|
|
|
+ // Handle (drop) incoming state packs:
|
|
|
+ case pack := <-d.stateCh:
|
|
|
+ req = active[pack.PeerId()]
|
|
|
+ reason = "delivered"
|
|
|
+ // Handle dropped peer connections:
|
|
|
+ case p := <-peerDrop:
|
|
|
+ req = active[p.id]
|
|
|
+ reason = "peerdrop"
|
|
|
+ // Handle timed-out requests:
|
|
|
+ case req = <-timeout:
|
|
|
+ reason = "timeout"
|
|
|
+ }
|
|
|
+ if req == nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ req.peer.log.Trace("State peer marked idle (spindown)", "req.items", len(req.items), "reason", reason)
|
|
|
+ req.timer.Stop()
|
|
|
+ delete(active, req.peer.id)
|
|
|
+ req.peer.SetNodeDataIdle(len(req.items))
|
|
|
+ }
|
|
|
+ // The 'finished' set contains deliveries that we were going to pass to processing.
|
|
|
+ // Those are now moot, but we still need to set those peers as idle, which would
|
|
|
+ // otherwise have been done after processing
|
|
|
+ for _, req := range finished {
|
|
|
+ req.peer.SetNodeDataIdle(len(req.items))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// stateSync schedules requests for downloading a particular state trie defined
|
|
|
// by a given state root.
|
|
|
type stateSync struct {
|
|
|
@@ -222,11 +260,15 @@ type stateSync struct {
|
|
|
numUncommitted int
|
|
|
bytesUncommitted int
|
|
|
|
|
|
+ started chan struct{} // Started is signalled once the sync loop starts
|
|
|
+
|
|
|
deliver chan *stateReq // Delivery channel multiplexing peer responses
|
|
|
cancel chan struct{} // Channel to signal a termination request
|
|
|
cancelOnce sync.Once // Ensures cancel only ever gets called once
|
|
|
done chan struct{} // Channel to signal termination completion
|
|
|
err error // Any error hit during sync (set before completion)
|
|
|
+
|
|
|
+ root common.Hash
|
|
|
}
|
|
|
|
|
|
// stateTask represents a single trie node download task, containing a set of
|
|
|
@@ -246,6 +288,8 @@ func newStateSync(d *Downloader, root common.Hash) *stateSync {
|
|
|
deliver: make(chan *stateReq),
|
|
|
cancel: make(chan struct{}),
|
|
|
done: make(chan struct{}),
|
|
|
+ started: make(chan struct{}),
|
|
|
+ root: root,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -276,6 +320,7 @@ func (s *stateSync) Cancel() error {
|
|
|
// pushed here async. The reason is to decouple processing from data receipt
|
|
|
// and timeouts.
|
|
|
func (s *stateSync) loop() (err error) {
|
|
|
+ close(s.started)
|
|
|
// Listen for new peer events to assign tasks to them
|
|
|
newPeer := make(chan *peerConnection, 1024)
|
|
|
peerSub := s.d.peers.SubscribeNewPeers(newPeer)
|
|
|
@@ -331,11 +376,11 @@ func (s *stateSync) loop() (err error) {
|
|
|
}
|
|
|
// Process all the received blobs and check for stale delivery
|
|
|
delivered, err := s.process(req)
|
|
|
+ req.peer.SetNodeDataIdle(delivered)
|
|
|
if err != nil {
|
|
|
log.Warn("Node data write error", "err", err)
|
|
|
return err
|
|
|
}
|
|
|
- req.peer.SetNodeDataIdle(delivered)
|
|
|
}
|
|
|
}
|
|
|
return nil
|
|
|
@@ -372,7 +417,7 @@ func (s *stateSync) assignTasks() {
|
|
|
|
|
|
// 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))
|
|
|
+ req.peer.log.Trace("Requesting new batch of data", "type", "state", "count", len(req.items), "root", s.root)
|
|
|
select {
|
|
|
case s.d.trackStateReq <- req:
|
|
|
req.peer.FetchNodeData(req.items)
|