les: new serving queue

This commit is contained in:
Zsolt Felfoldi 2019-01-28 22:01:37 +01:00
parent b3f5a40502
commit a67f0ae4ad
2 changed files with 283 additions and 340 deletions

View file

@ -336,15 +336,15 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
responseCount := p.responseCount
var (
maxCost uint64
priority int64
task *servingTask
)
reject := func(reqID, reqCnt, maxCnt uint64) bool {
accept := func(reqID, reqCnt, maxCnt uint64) bool {
if reqCnt == 0 {
return true
return false
}
if p.fcClient == nil || reqCnt > maxCnt {
return true
return false
}
maxCost = p.fcCosts.getCost(msg.Code, reqCnt)
@ -352,11 +352,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
if bufShort > 0 {
p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
}
return true
} else {
priority = servingPriority
}
return false
} else {
task = pm.servingQueue.newTask(servingPriority)
}
return task.start()
}
if msg.Size > ProtocolMaxMsgSize {
@ -366,12 +366,6 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
var deliverMsg *Msg
errorFn := func(err error) {
if err != nil {
p.errCh <- err
}
}
sendReq := func(reqID, amount uint64, reply *reply, servingTime uint64) {
p.responseLock.Lock()
defer p.responseLock.Unlock()
@ -448,10 +442,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
}
query := req.Query
if reject(req.ReqID, query.Amount, MaxHeaderFetch) {
if !accept(req.ReqID, query.Amount, MaxHeaderFetch) {
return errResp(ErrRequestRejected, "")
}
go func() {
hashMode := query.Origin.Hash != (common.Hash{})
first := true
maxNonCanonical := uint64(100)
@ -462,10 +456,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
headers []*types.Header
unknown bool
)
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
for !unknown && len(headers) < int(query.Amount) && bytes < softResponseLimit {
// Retrieve the next header satisfying the query
var origin *types.Header
if hashMode {
@ -482,7 +473,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
origin = pm.blockchain.GetHeaderByNumber(query.Origin.Number)
}
if origin == nil {
return true, nil
break
}
headers = append(headers, origin)
bytes += estHeaderRlpSize
@ -533,14 +524,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
// Number based traversal towards the leaf block
query.Origin.Number += query.Skip + 1
}
return unknown || len(headers) >= int(query.Amount) || bytes >= softResponseLimit, nil
},
// after: sendFunc(query.Amount, func(bv uint64) error { return p.SendBlockHeaders(req.ReqID, bv, headers) }),
send: func(servingTime uint64) {
sendReq(req.ReqID, query.Amount, p.ReplyBlockHeaders(req.ReqID, headers), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, query.Amount, p.ReplyBlockHeaders(req.ReqID, headers), task.done())
}()
case BlockHeadersMsg:
if pm.downloader == nil {
@ -582,18 +568,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
bodies []rlp.RawValue
)
reqCnt := len(req.Hashes)
if reject(req.ReqID, uint64(reqCnt), MaxBodyFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxBodyFetch) {
return errResp(ErrRequestRejected, "")
}
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
hash := req.Hashes[index]
index++
go func() {
for _, hash := range req.Hashes {
if bytes >= softResponseLimit {
return true, nil
break
}
// Retrieve the requested block body, stopping if enough was found
if number := rawdb.ReadHeaderNumber(pm.chainDb, hash); number != nil {
@ -602,13 +583,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
bytes += len(data)
}
}
return index == reqCnt, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyBlockBodiesRLP(req.ReqID, bodies), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyBlockBodiesRLP(req.ReqID, bodies), task.done())
}()
case BlockBodiesMsg:
if pm.odr == nil {
@ -647,42 +624,33 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
data [][]byte
)
reqCnt := len(req.Reqs)
if reject(req.ReqID, uint64(reqCnt), MaxCodeFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxCodeFetch) {
return errResp(ErrRequestRejected, "")
}
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
req := req.Reqs[index]
index++
done := index == reqCnt
go func() {
for _, req := range req.Reqs {
// Retrieve the requested state entry, stopping if enough was found
if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil {
if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil {
statedb, err := pm.blockchain.State()
if err != nil {
return done, nil
continue
}
account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey))
if err != nil {
return done, nil
continue
}
code, _ := statedb.Database().TrieDB().Node(common.BytesToHash(account.CodeHash))
data = append(data, code)
if bytes += len(code); bytes >= softResponseLimit {
return true, nil
break
}
}
}
return done, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyCode(req.ReqID, data), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyCode(req.ReqID, data), task.done())
}()
case CodeMsg:
if pm.odr == nil {
@ -721,19 +689,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
receipts []rlp.RawValue
)
reqCnt := len(req.Hashes)
if reject(req.ReqID, uint64(reqCnt), MaxReceiptFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxReceiptFetch) {
return errResp(ErrRequestRejected, "")
}
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
hash := req.Hashes[index]
index++
done := index == reqCnt
go func() {
for _, hash := range req.Hashes {
if bytes >= softResponseLimit {
return true, nil
break
}
// Retrieve the requested block's receipts, skipping if unknown to us
var results types.Receipts
@ -742,7 +704,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
}
if results == nil {
if header := pm.blockchain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash {
return done, nil
continue
}
}
// If known, encode and queue for response packet
@ -752,13 +714,9 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
receipts = append(receipts, encoded)
bytes += len(encoded)
}
return done, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyReceiptsRLP(req.ReqID, receipts), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyReceiptsRLP(req.ReqID, receipts), task.done())
}()
case ReceiptsMsg:
if pm.odr == nil {
@ -797,29 +755,23 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
proofs proofsData
)
reqCnt := len(req.Reqs)
if reject(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
return errResp(ErrRequestRejected, "")
}
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
req := req.Reqs[index]
index++
done := index == reqCnt
go func() {
for _, req := range req.Reqs {
// Retrieve the requested state entry, stopping if enough was found
if number := rawdb.ReadHeaderNumber(pm.chainDb, req.BHash); number != nil {
if header := rawdb.ReadHeader(pm.chainDb, req.BHash, *number); header != nil {
statedb, err := pm.blockchain.State()
if err != nil {
return done, nil
continue
}
var trie state.Trie
if len(req.AccKey) > 0 {
account, err := pm.getAccount(statedb, header.Root, common.BytesToHash(req.AccKey))
if err != nil {
return done, nil
continue
}
trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root)
} else {
@ -831,18 +783,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
proofs = append(proofs, proof)
if bytes += proof.DataSize(); bytes >= softResponseLimit {
return true, nil
break
}
}
}
}
return done, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyProofs(req.ReqID, proofs), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyProofs(req.ReqID, proofs), task.done())
}()
case GetProofsV2Msg:
p.Log().Trace("Received les/2 proofs request")
@ -861,19 +809,14 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
root common.Hash
)
reqCnt := len(req.Reqs)
if reject(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxProofsFetch) {
return errResp(ErrRequestRejected, "")
}
go func() {
nodes := light.NewNodeSet()
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
req := req.Reqs[index]
index++
done := index == reqCnt
for _, req := range req.Reqs {
// Look up the state belonging to the request
if statedb == nil || req.BHash != lastBHash {
statedb, root, lastBHash = nil, common.Hash{}, req.BHash
@ -886,34 +829,30 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
}
}
if statedb == nil {
return done, nil
continue
}
// Pull the account or storage trie of the request
var trie state.Trie
if len(req.AccKey) > 0 {
account, err := pm.getAccount(statedb, root, common.BytesToHash(req.AccKey))
if err != nil {
return done, nil
continue
}
trie, _ = statedb.Database().OpenStorageTrie(common.BytesToHash(req.AccKey), account.Root)
} else {
trie, _ = statedb.Database().OpenTrie(root)
}
if trie == nil {
return done, nil
continue
}
// Prove the user's request from the account or stroage trie
trie.Prove(req.Key, req.FromLevel, nodes)
if nodes.DataSize() >= softResponseLimit {
return true, nil
break
}
return done, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyProofsV2(req.ReqID, nodes.NodeList()), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyProofsV2(req.ReqID, nodes.NodeList()), task.done())
}()
case ProofsV1Msg:
if pm.odr == nil {
@ -973,24 +912,18 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
proofs []ChtResp
)
reqCnt := len(req.Reqs)
if reject(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
return errResp(ErrRequestRejected, "")
}
go func() {
trieDb := trie.NewDatabase(ethdb.NewTable(pm.chainDb, light.ChtTablePrefix))
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
req := req.Reqs[index]
index++
done := index == reqCnt
for _, req := range req.Reqs {
if header := pm.blockchain.GetHeaderByNumber(req.BlockNum); header != nil {
sectionHead := rawdb.ReadCanonicalHash(pm.chainDb, req.ChtNum*pm.iConfig.ChtSize-1)
if root := light.GetChtRoot(pm.chainDb, req.ChtNum-1, sectionHead); root != (common.Hash{}) {
trie, err := trie.New(root, trieDb)
if err != nil {
return done, nil
continue
}
var encNumber [8]byte
binary.BigEndian.PutUint64(encNumber[:], req.BlockNum)
@ -1000,17 +933,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
proofs = append(proofs, ChtResp{Header: header, Proof: proof})
if bytes += proof.DataSize() + estHeaderRlpSize; bytes >= softResponseLimit {
return true, nil
break
}
}
}
return done, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyHeaderProofs(req.ReqID, proofs), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyHeaderProofs(req.ReqID, proofs), task.done())
}()
case GetHelperTrieProofsMsg:
p.Log().Trace("Received helper trie proof request")
@ -1028,9 +957,10 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
auxData [][]byte
)
reqCnt := len(req.Reqs)
if reject(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
if !accept(req.ReqID, uint64(reqCnt), MaxHelperTrieProofsFetch) {
return errResp(ErrRequestRejected, "")
}
go func() {
var (
lastIdx uint64
@ -1039,13 +969,7 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
auxTrie *trie.Trie
)
nodes := light.NewNodeSet()
index := 0
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
req := req.Reqs[index]
index++
for _, req := range req.Reqs {
if auxTrie == nil || req.Type != lastType || req.TrieIdx != lastIdx {
auxTrie, lastType, lastIdx = nil, req.Type, req.TrieIdx
@ -1072,15 +996,11 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
}
}
if nodes.DataSize()+auxBytes >= softResponseLimit {
return true, nil
break
}
return index == reqCnt, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}), servingTime)
},
fail: errorFn,
})
}
sendReq(req.ReqID, uint64(reqCnt), p.ReplyHelperTrieProofs(req.ReqID, HelperTrieResps{Proofs: nodes.NodeList(), AuxData: auxData}), task.done())
}()
case HeaderProofsMsg:
if pm.odr == nil {
@ -1133,21 +1053,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
reqCnt := len(txs)
if reject(0, uint64(reqCnt), MaxTxSend) {
if !accept(0, uint64(reqCnt), MaxTxSend) {
return errResp(ErrRequestRejected, "")
}
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
go func() {
pm.txpool.AddRemotes(txs)
return true, nil
},
send: func(servingTime uint64) {
sendReq(0, uint64(reqCnt), nil, servingTime)
},
fail: errorFn,
})
sendReq(0, uint64(reqCnt), nil, task.done())
}()
case SendTxV2Msg:
if pm.txpool == nil {
@ -1162,19 +1074,15 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
reqCnt := len(req.Txs)
if reject(req.ReqID, uint64(reqCnt), MaxTxSend) {
if !accept(req.ReqID, uint64(reqCnt), MaxTxSend) {
return errResp(ErrRequestRejected, "")
}
var stats []txStatus
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
go func() {
hashes := make([]common.Hash, len(req.Txs))
for i, tx := range req.Txs {
hashes[i] = tx.Hash()
}
stats = pm.txStatus(hashes)
stats := pm.txStatus(hashes)
for i, stat := range stats {
if stat.Status == core.TxStatusUnknown {
if errs := pm.txpool.AddRemotes([]*types.Transaction{req.Txs[i]}); errs[0] != nil {
@ -1184,13 +1092,8 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
stats[i] = pm.txStatus([]common.Hash{hashes[i]})[0]
}
}
return true, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), servingTime)
},
fail: errorFn,
})
sendReq(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), task.done())
}()
case GetTxStatusMsg:
if pm.txpool == nil {
@ -1205,22 +1108,13 @@ func (pm *ProtocolManager) handleMsg(p *peer) error {
return errResp(ErrDecode, "msg %v: %v", msg, err)
}
reqCnt := len(req.Hashes)
if reject(req.ReqID, uint64(reqCnt), MaxTxStatus) {
if !accept(req.ReqID, uint64(reqCnt), MaxTxStatus) {
return errResp(ErrRequestRejected, "")
}
var stats []txStatus
pm.servingQueue.addTask(&servingTask{
priority: priority,
run: func() (bool, error) {
stats = pm.txStatus(req.Hashes)
return true, nil
},
send: func(servingTime uint64) {
sendReq(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), servingTime)
},
fail: errorFn,
})
go func() {
stats := pm.txStatus(req.Hashes)
sendReq(req.ReqID, uint64(reqCnt), p.ReplyTxStatus(req.ReqID, stats), task.done())
}()
case TxStatusMsg:
if pm.odr == nil {

View file

@ -26,12 +26,15 @@ import (
// servingQueue runs serving tasks in a limited number of threads and puts the
// waiting tasks in a priority queue
type servingQueue struct {
lock sync.Mutex
tokenCh chan runToken
queueAddCh, queueBestCh chan *servingTask
stopThreadCh, quit chan struct{}
setThreadsCh chan int
wg sync.WaitGroup
threadCount int // number of currently running threads
stopCount int // number of threads to be stopped after they finish their current task
queue *prque.Prque // priority queue for waiting or suspended tasks
best *servingTask // either best == nil (queue empty) or waitingForTask is empty
waiting []chan *servingTask // threads waiting for a task
suspendBias int64 // priority bias against suspending an already running task
}
@ -44,145 +47,191 @@ type servingQueue struct {
// - run: execute a single step; return true if finished
// - after: executed after run finishes or returns an error, receives the total serving time
type servingTask struct {
sq *servingQueue
servingTime uint64
done bool
err error
priority int64
run func() (finished bool, err error)
send func(servingTime uint64)
fail func(err error)
biasAdded bool
token runToken
tokenCh chan runToken
}
type runToken chan struct{}
func (t *servingTask) start() bool {
select {
case t.token = <-t.sq.tokenCh:
default:
t.tokenCh = make(chan runToken, 1)
select {
case t.sq.queueAddCh <- t:
case <-t.sq.quit:
return false
}
select {
case t.token = <-t.tokenCh:
case <-t.sq.quit:
return false
}
}
if t.token == nil {
return false
}
t.servingTime -= uint64(mclock.Now())
return true
}
func (t *servingTask) done() uint64 {
t.servingTime += uint64(mclock.Now())
close(t.token)
return t.servingTime
}
func (t *servingTask) waitOrStop() bool {
t.done()
if !t.biasAdded {
t.priority += t.sq.suspendBias
t.biasAdded = true
}
return t.start()
}
// newServingQueue returns a new servingQueue
func newServingQueue(_suspendBias int64) *servingQueue {
return &servingQueue{
func newServingQueue(suspendBias int64) *servingQueue {
sq := &servingQueue{
queue: prque.New(nil),
suspendBias: _suspendBias,
suspendBias: suspendBias,
tokenCh: make(chan runToken),
queueAddCh: make(chan *servingTask, 100),
queueBestCh: make(chan *servingTask),
stopThreadCh: make(chan struct{}),
quit: make(chan struct{}),
setThreadsCh: make(chan int, 10),
}
sq.wg.Add(2)
go sq.queueLoop()
go sq.threadCountLoop()
return sq
}
func (sq *servingQueue) newTask(priority int64) *servingTask {
return &servingTask{
sq: sq,
priority: priority,
}
}
// addTask adds a new task, either starting it immediately or queueing it
func (sq *servingQueue) addTask(task *servingTask) {
sq.lock.Lock()
defer sq.lock.Unlock()
if l := len(sq.waiting); l != 0 {
l--
sq.waiting[l] <- task
sq.waiting = sq.waiting[:l]
func (sq *servingQueue) threadController() {
for {
token := make(runToken)
select {
case best := <-sq.queueBestCh:
best.tokenCh <- token
default:
select {
case best := <-sq.queueBestCh:
best.tokenCh <- token
case sq.tokenCh <- token:
case <-sq.stopThreadCh:
sq.wg.Done()
return
case <-sq.quit:
sq.wg.Done()
return
}
}
<-token
select {
case <-sq.stopThreadCh:
sq.wg.Done()
return
case <-sq.quit:
sq.wg.Done()
return
default:
}
}
}
func (sq *servingQueue) addTask(task *servingTask) {
if sq.best == nil {
sq.best = task
return
}
if task.priority < sq.best.priority {
} else if task.priority > sq.best.priority {
sq.queue.Push(sq.best, sq.best.priority)
sq.best = task
return
}
} else {
sq.queue.Push(task, task.priority)
}
}
// getNewTask selects a new task to be processed. If blocking == true then it waits
// until a runnable task arrives or returns nil if the thread should be stopped.
// if currentTask != nil then it returns immediately and only returns a new task
// if the current one should be suspended.
// Note: either blocking should be false or currentTask should be nil.
func (sq *servingQueue) getNewTask(currentTask *servingTask, blocking bool) *servingTask {
sq.lock.Lock()
if sq.stopCount == 0 {
if sq.best != nil && (currentTask == nil || sq.best.priority <= currentTask.priority-sq.suspendBias) {
best := sq.best
func (sq *servingQueue) queueLoop() {
for {
if sq.best != nil {
select {
case task := <-sq.queueAddCh:
sq.addTask(task)
case sq.queueBestCh <- sq.best:
if sq.queue.Size() == 0 {
sq.best = nil
} else {
sq.best, _ = sq.queue.PopItem().(*servingTask)
}
sq.lock.Unlock()
return best
}
if blocking {
ch := make(chan *servingTask)
sq.waiting = append(sq.waiting, ch)
sq.lock.Unlock()
return <-ch
case <-sq.quit:
sq.wg.Done()
return
}
} else {
sq.stopCount--
sq.threadCount--
select {
case task := <-sq.queueAddCh:
sq.addTask(task)
case <-sq.quit:
sq.wg.Done()
return
}
}
}
}
func (sq *servingQueue) threadCountLoop() {
var threadCountTarget int
for {
for threadCountTarget > sq.threadCount {
sq.wg.Add(1)
go sq.threadController()
sq.threadCount++
}
if threadCountTarget < sq.threadCount {
select {
case threadCountTarget = <-sq.setThreadsCh:
case sq.stopThreadCh <- struct{}{}:
sq.threadCount--
case <-sq.quit:
sq.wg.Done()
return
}
} else {
select {
case threadCountTarget = <-sq.setThreadsCh:
case <-sq.quit:
sq.wg.Done()
return
}
}
}
sq.lock.Unlock()
return nil
}
// setThreads sets the processing thread count, suspending tasks as soon as
// possible if necessary.
func (sq *servingQueue) setThreads(threadCount int) {
sq.lock.Lock()
defer sq.lock.Unlock()
diff := threadCount - sq.threadCount + sq.stopCount
if diff > 0 {
// start more threads
if sq.stopCount >= diff {
sq.stopCount -= diff
} else {
diff -= sq.stopCount
sq.stopCount = 0
sq.threadCount += diff
for ; diff > 0; diff-- {
go sq.servingThread()
}
}
}
if diff < 0 {
// stop some threads
lw := len(sq.waiting)
sq.stopCount -= diff
for diff < 0 && lw > 0 {
diff++
lw--
sq.waiting[lw] <- nil
sq.stopCount--
sq.threadCount--
}
sq.waiting = sq.waiting[:lw]
select {
case sq.setThreadsCh <- threadCount:
case <-sq.quit:
return
}
}
// stop stops task processing as soon as possible
func (sq *servingQueue) stop() {
sq.setThreads(0)
}
// servingThread implements a single serving thread
func (sq *servingQueue) servingThread() {
for {
task := sq.getNewTask(nil, true)
if task == nil {
return
}
task.servingTime -= uint64(mclock.Now())
for {
task.done, task.err = task.run()
if task.done || task.err != nil {
task.servingTime += uint64(mclock.Now())
if task.err == nil {
task.send(task.servingTime)
} else {
task.fail(task.err)
}
break
}
if newTask := sq.getNewTask(task, false); newTask != nil {
now := uint64(mclock.Now())
task.servingTime += now
sq.addTask(task)
task = newTask
task.servingTime -= now
}
}
}
close(sq.quit)
sq.wg.Wait()
}