@ -23,7 +23,6 @@ import (
"errors"
"fmt"
"sync"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
@ -101,10 +100,9 @@ type queue struct {
stateTaskQueue * prque . Prque // [eth/63] Priority queue of the hashes to fetch the node data for
statePendPool map [ string ] * fetchRequest // [eth/63] Currently pending node data retrieval operations
stateDatabase ethdb . Database // [eth/63] Trie database to populate during state reassembly
stateScheduler * state . StateSync // [eth/63] State trie synchronisation scheduler and integrator
stateProcessors int32 // [eth/63] Number of currently running state processors
stateSchedLock sync . RWMutex // [eth/63] Lock serialising access to the state scheduler
stateDatabase ethdb . Database // [eth/63] Trie database to populate during state reassembly
stateScheduler * state . StateSync // [eth/63] State trie synchronisation scheduler and integrator
stateWriters int // [eth/63] Number of running state DB writer goroutines
resultCache [ ] * fetchResult // Downloaded but not yet delivered fetch results
resultOffset uint64 // Offset of the first cached fetch result in the block chain
@ -143,9 +141,6 @@ func (q *queue) Reset() {
q . lock . Lock ( )
defer q . lock . Unlock ( )
q . stateSchedLock . Lock ( )
defer q . stateSchedLock . Unlock ( )
q . closed = false
q . mode = FullSync
q . fastSyncPivot = 0
@ -209,13 +204,24 @@ func (q *queue) PendingReceipts() int {
// PendingNodeData retrieves the number of node data entries pending for retrieval.
func ( q * queue ) PendingNodeData ( ) int {
q . stateSchedL ock. R Lock( )
defer q . stateSchedL ock. R Unlock( )
q . l ock. Lock ( )
defer q . l ock. Unlock ( )
return q . pendingNodeDataLocked ( )
}
// pendingNodeDataLocked retrieves the number of node data entries pending for retrieval.
// The caller must hold q.lock.
func ( q * queue ) pendingNodeDataLocked ( ) int {
var n int
if q . stateScheduler != nil {
return q . stateScheduler . Pending ( )
n = q . stateScheduler . Pending ( )
}
// Ensure that PendingNodeData doesn't return 0 until all state is written.
if q . stateWriters > 0 {
n ++
}
return 0
return n
}
// InFlightHeaders retrieves whether there are header fetch requests currently
@ -251,7 +257,7 @@ func (q *queue) InFlightNodeData() bool {
q . lock . Lock ( )
defer q . lock . Unlock ( )
return len ( q . statePendPool ) + int ( atomic . LoadInt32 ( & q . stateProcessors ) ) > 0
return len ( q . statePendPool ) + q . stateWriters > 0
}
// Idle returns if the queue is fully idle or has some data still inside. This
@ -264,12 +270,9 @@ func (q *queue) Idle() bool {
pending := len ( q . blockPendPool ) + len ( q . receiptPendPool ) + len ( q . statePendPool )
cached := len ( q . blockDonePool ) + len ( q . receiptDonePool )
q . stateSchedLock . RLock ( )
if q . stateScheduler != nil {
queued += q . stateScheduler . Pending ( )
}
q . stateSchedLock . RUnlock ( )
return ( queued + pending + cached ) == 0
}
@ -398,9 +401,7 @@ func (q *queue) Schedule(headers []*types.Header, from uint64) []*types.Header {
req . Hashes = make ( map [ common . Hash ] int ) // Make sure executing requests fail, but don't disappear
}
q . stateSchedLock . Lock ( )
q . stateScheduler = state . NewStateSync ( header . Root , q . stateDatabase )
q . stateSchedLock . Unlock ( )
}
inserts = append ( inserts , header )
q . headerHead = hash
@ -459,7 +460,7 @@ func (q *queue) countProcessableItems() int {
// resultCache has space for fsHeaderForceVerify items. Not
// doing this could leave us unable to download the required
// amount of headers.
if i > 0 || len ( q . stateTaskPool ) > 0 || q . PendingNodeData ( ) > 0 {
if i > 0 || len ( q . stateTaskPool ) > 0 || q . pendingNodeDataLocked ( ) > 0 {
return i
}
for j := 0 ; j < fsHeaderForceVerify ; j ++ {
@ -524,9 +525,6 @@ func (q *queue) ReserveHeaders(p *peer, count int) *fetchRequest {
func ( q * queue ) ReserveNodeData ( p * peer , count int ) * fetchRequest {
// Create a task generator to fetch status-fetch tasks if all schedules ones are done
generator := func ( max int ) {
q . stateSchedLock . Lock ( )
defer q . stateSchedLock . Unlock ( )
if q . stateScheduler != nil {
for _ , hash := range q . stateScheduler . Missing ( max ) {
q . stateTaskPool [ hash ] = q . stateTaskIndex
@ -1068,7 +1066,7 @@ func (q *queue) DeliverNodeData(id string, data [][]byte, callback func(int, boo
}
}
// Iterate over the downloaded data and verify each of them
accepted , errs := 0 , make ( [ ] error , 0 )
errs := make ( [ ] error , 0 )
process := [ ] trie . SyncResult { }
for _ , blob := range data {
// Skip any state trie entries that were not requested
@ -1079,68 +1077,50 @@ func (q *queue) DeliverNodeData(id string, data [][]byte, callback func(int, boo
}
// Inject the next state trie item into the processing queue
process = append ( process , trie . SyncResult { Hash : hash , Data : blob } )
accepted ++
delete ( request . Hashes , hash )
delete ( q . stateTaskPool , hash )
}
// Start the asynchronous node state data injection
atomic . AddInt32 ( & q . stateProcessors , 1 )
go func ( ) {
defer atomic . AddInt32 ( & q . stateProcessors , - 1 )
q . deliverNodeData ( process , callback )
} ( )
// Return all failed or missing fetches to the queue
for hash , index := range request . Hashes {
q . stateTaskQueue . Push ( hash , float32 ( index ) )
}
if q . stateScheduler == nil {
return 0 , errNoFetchesPending
}
// Run valid nodes through the trie download scheduler. It writes completed nodes to a
// batch, which is committed asynchronously. This may lead to over-fetches because the
// scheduler treats everything as written after Process has returned, but it's
// unlikely to be an issue in practice.
batch := q . stateDatabase . NewBatch ( )
progressed , nproc , procerr := q . stateScheduler . Process ( process , batch )
q . stateWriters += 1
go func ( ) {
if procerr == nil {
nproc = len ( process )
procerr = batch . Write ( )
}
// Return processing errors through the callback so the sync gets canceled. The
// number of writers is decremented prior to the call so PendingNodeData will
// return zero when the callback runs.
q . lock . Lock ( )
q . stateWriters -= 1
q . lock . Unlock ( )
callback ( nproc , progressed , procerr )
// Wake up WaitResults after the state has been written because it might be
// waiting for completion of the pivot block's state download.
q . active . Signal ( )
} ( )
// If none of the data items were good, it's a stale delivery
switch {
case len ( errs ) == 0 :
return accepted , nil
return len ( process ) , nil
case len ( errs ) == len ( request . Hashes ) :
return accepted , errStaleDelivery
return len ( process ) , errStaleDelivery
default :
return accepted , fmt . Errorf ( "multiple failures: %v" , errs )
}
}
// deliverNodeData is the asynchronous node data processor that injects a batch
// of sync results into the state scheduler.
func ( q * queue ) deliverNodeData ( results [ ] trie . SyncResult , callback func ( int , bool , error ) ) {
// Wake up WaitResults after the state has been written because it
// might be waiting for the pivot block state to get completed.
defer q . active . Signal ( )
// Process results one by one to permit task fetches in between
progressed := false
for i , result := range results {
q . stateSchedLock . Lock ( )
if q . stateScheduler == nil {
// Syncing aborted since this async delivery started, bail out
q . stateSchedLock . Unlock ( )
callback ( i , progressed , errNoFetchesPending )
return
}
batch := q . stateDatabase . NewBatch ( )
prog , _ , err := q . stateScheduler . Process ( [ ] trie . SyncResult { result } , batch )
if err != nil {
q . stateSchedLock . Unlock ( )
callback ( i , progressed , err )
return
}
if err = batch . Write ( ) ; err != nil {
q . stateSchedLock . Unlock ( )
callback ( i , progressed , err )
return // TODO(karalabe): If a DB write fails (disk full), we ought to cancel the sync
}
// Item processing succeeded, release the lock (temporarily)
progressed = progressed || prog
q . stateSchedLock . Unlock ( )
return len ( process ) , fmt . Errorf ( "multiple failures: %v" , errs )
}
callback ( len ( results ) , progressed , nil )
}
// Prepare configures the result cache to allow accepting and caching inbound