|
|
@@ -204,6 +204,90 @@ func (h *serverHandler) handle(p *clientPeer) error {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// beforeHandle will do a series of prechecks before handling message.
|
|
|
+func (h *serverHandler) beforeHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, reqCnt uint64, maxCount uint64) (*servingTask, uint64) {
|
|
|
+ // Ensure that the request sent by client peer is valid
|
|
|
+ inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0)
|
|
|
+ if reqCnt == 0 || reqCnt > maxCount {
|
|
|
+ p.fcClient.OneTimeCost(inSizeCost)
|
|
|
+ return nil, 0
|
|
|
+ }
|
|
|
+ // Ensure that the client peer complies with the flow control
|
|
|
+ // rules agreed by both sides.
|
|
|
+ if p.isFrozen() {
|
|
|
+ p.fcClient.OneTimeCost(inSizeCost)
|
|
|
+ return nil, 0
|
|
|
+ }
|
|
|
+ maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt)
|
|
|
+ accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost)
|
|
|
+ if !accepted {
|
|
|
+ p.freeze()
|
|
|
+ p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
|
|
|
+ p.fcClient.OneTimeCost(inSizeCost)
|
|
|
+ return nil, 0
|
|
|
+ }
|
|
|
+ // Create a multi-stage task, estimate the time it takes for the task to
|
|
|
+ // execute, and cache it in the request service queue.
|
|
|
+ factor := h.server.costTracker.globalFactor()
|
|
|
+ if factor < 0.001 {
|
|
|
+ factor = 1
|
|
|
+ p.Log().Error("Invalid global cost factor", "factor", factor)
|
|
|
+ }
|
|
|
+ maxTime := uint64(float64(maxCost) / factor)
|
|
|
+ task := h.server.servingQueue.newTask(p, maxTime, priority)
|
|
|
+ if !task.start() {
|
|
|
+ p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost)
|
|
|
+ return nil, 0
|
|
|
+ }
|
|
|
+ return task, maxCost
|
|
|
+}
|
|
|
+
|
|
|
+// Afterhandle will perform a series of operations after message handling,
|
|
|
+// such as updating flow control data, sending reply, etc.
|
|
|
+func (h *serverHandler) afterHandle(p *clientPeer, reqID, responseCount uint64, msg p2p.Msg, maxCost uint64, reqCnt uint64, task *servingTask, reply *reply) {
|
|
|
+ if reply != nil {
|
|
|
+ task.done()
|
|
|
+ }
|
|
|
+ p.responseLock.Lock()
|
|
|
+ defer p.responseLock.Unlock()
|
|
|
+
|
|
|
+ // Short circuit if the client is already frozen.
|
|
|
+ if p.isFrozen() {
|
|
|
+ realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0)
|
|
|
+ p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ // Positive correction buffer value with real cost.
|
|
|
+ var replySize uint32
|
|
|
+ if reply != nil {
|
|
|
+ replySize = reply.size()
|
|
|
+ }
|
|
|
+ var realCost uint64
|
|
|
+ if h.server.costTracker.testing {
|
|
|
+ realCost = maxCost // Assign a fake cost for testing purpose
|
|
|
+ } else {
|
|
|
+ realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize)
|
|
|
+ if realCost > maxCost {
|
|
|
+ realCost = maxCost
|
|
|
+ }
|
|
|
+ }
|
|
|
+ bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
|
|
|
+ if reply != nil {
|
|
|
+ // Feed cost tracker request serving statistic.
|
|
|
+ h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost)
|
|
|
+ // Reduce priority "balance" for the specific peer.
|
|
|
+ p.balance.RequestServed(realCost)
|
|
|
+ p.queueSend(func() {
|
|
|
+ if err := reply.send(bv); err != nil {
|
|
|
+ select {
|
|
|
+ case p.errCh <- err:
|
|
|
+ default:
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// handleMsg is invoked whenever an inbound message is received from a remote
|
|
|
// peer. The remote connection is torn down upon returning any error.
|
|
|
func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
|
|
|
@@ -221,9 +305,8 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
|
|
|
}
|
|
|
defer msg.Discard()
|
|
|
|
|
|
- p.responseCount++
|
|
|
- responseCount := p.responseCount
|
|
|
-
|
|
|
+ // Lookup the request handler table, ensure it's supported
|
|
|
+ // message type by the protocol.
|
|
|
req, ok := Les3[msg.Code]
|
|
|
if !ok {
|
|
|
p.Log().Trace("Received invalid message", "code", msg.Code)
|
|
|
@@ -232,98 +315,42 @@ func (h *serverHandler) handleMsg(p *clientPeer, wg *sync.WaitGroup) error {
|
|
|
}
|
|
|
p.Log().Trace("Received " + req.Name)
|
|
|
|
|
|
+ // Decode the p2p message, resolve the concrete handler for it.
|
|
|
serve, reqID, reqCnt, err := req.Handle(msg)
|
|
|
if err != nil {
|
|
|
clientErrorMeter.Mark(1)
|
|
|
return errResp(ErrDecode, "%v: %v", msg, err)
|
|
|
}
|
|
|
-
|
|
|
if metrics.EnabledExpensive {
|
|
|
req.InPacketsMeter.Mark(1)
|
|
|
req.InTrafficMeter.Mark(int64(msg.Size))
|
|
|
}
|
|
|
+ p.responseCount++
|
|
|
+ responseCount := p.responseCount
|
|
|
|
|
|
- // Short circuit if the peer is already frozen or the request is invalid.
|
|
|
- inSizeCost := h.server.costTracker.realCost(0, msg.Size, 0)
|
|
|
- if p.isFrozen() || reqCnt == 0 || reqCnt > req.MaxCount {
|
|
|
- p.fcClient.OneTimeCost(inSizeCost)
|
|
|
- return nil
|
|
|
- }
|
|
|
- // Prepaid max cost units before request been serving.
|
|
|
- maxCost := p.fcCosts.getMaxCost(msg.Code, reqCnt)
|
|
|
- accepted, bufShort, priority := p.fcClient.AcceptRequest(reqID, responseCount, maxCost)
|
|
|
- if !accepted {
|
|
|
- p.freeze()
|
|
|
- p.Log().Error("Request came too early", "remaining", common.PrettyDuration(time.Duration(bufShort*1000000/p.fcParams.MinRecharge)))
|
|
|
- p.fcClient.OneTimeCost(inSizeCost)
|
|
|
+ // First check this client message complies all rules before
|
|
|
+ // handling it and return a processor if all checks are passed.
|
|
|
+ task, maxCost := h.beforeHandle(p, reqID, responseCount, msg, reqCnt, req.MaxCount)
|
|
|
+ if task == nil {
|
|
|
return nil
|
|
|
}
|
|
|
- // Create a multi-stage task, estimate the time it takes for the task to
|
|
|
- // execute, and cache it in the request service queue.
|
|
|
- factor := h.server.costTracker.globalFactor()
|
|
|
- if factor < 0.001 {
|
|
|
- factor = 1
|
|
|
- p.Log().Error("Invalid global cost factor", "factor", factor)
|
|
|
- }
|
|
|
- maxTime := uint64(float64(maxCost) / factor)
|
|
|
- task := h.server.servingQueue.newTask(p, maxTime, priority)
|
|
|
- if task.start() {
|
|
|
- wg.Add(1)
|
|
|
- go func() {
|
|
|
- defer wg.Done()
|
|
|
- reply := serve(h, p, task.waitOrStop)
|
|
|
- if reply != nil {
|
|
|
- task.done()
|
|
|
- }
|
|
|
+ wg.Add(1)
|
|
|
+ go func() {
|
|
|
+ defer wg.Done()
|
|
|
|
|
|
- p.responseLock.Lock()
|
|
|
- defer p.responseLock.Unlock()
|
|
|
+ reply := serve(h, p, task.waitOrStop)
|
|
|
+ h.afterHandle(p, reqID, responseCount, msg, maxCost, reqCnt, task, reply)
|
|
|
|
|
|
- // Short circuit if the client is already frozen.
|
|
|
- if p.isFrozen() {
|
|
|
- realCost := h.server.costTracker.realCost(task.servingTime, msg.Size, 0)
|
|
|
- p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
|
|
|
- return
|
|
|
- }
|
|
|
- // Positive correction buffer value with real cost.
|
|
|
- var replySize uint32
|
|
|
+ if metrics.EnabledExpensive {
|
|
|
+ size := uint32(0)
|
|
|
if reply != nil {
|
|
|
- replySize = reply.size()
|
|
|
- }
|
|
|
- var realCost uint64
|
|
|
- if h.server.costTracker.testing {
|
|
|
- realCost = maxCost // Assign a fake cost for testing purpose
|
|
|
- } else {
|
|
|
- realCost = h.server.costTracker.realCost(task.servingTime, msg.Size, replySize)
|
|
|
- if realCost > maxCost {
|
|
|
- realCost = maxCost
|
|
|
- }
|
|
|
+ size = reply.size()
|
|
|
}
|
|
|
- bv := p.fcClient.RequestProcessed(reqID, responseCount, maxCost, realCost)
|
|
|
- if reply != nil {
|
|
|
- // Feed cost tracker request serving statistic.
|
|
|
- h.server.costTracker.updateStats(msg.Code, reqCnt, task.servingTime, realCost)
|
|
|
- // Reduce priority "balance" for the specific peer.
|
|
|
- p.balance.RequestServed(realCost)
|
|
|
- p.queueSend(func() {
|
|
|
- if err := reply.send(bv); err != nil {
|
|
|
- select {
|
|
|
- case p.errCh <- err:
|
|
|
- default:
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- if metrics.EnabledExpensive {
|
|
|
- req.OutPacketsMeter.Mark(1)
|
|
|
- req.OutTrafficMeter.Mark(int64(replySize))
|
|
|
- req.ServingTimeMeter.Update(time.Duration(task.servingTime))
|
|
|
- }
|
|
|
- }
|
|
|
- }()
|
|
|
- } else {
|
|
|
- p.fcClient.RequestProcessed(reqID, responseCount, maxCost, inSizeCost)
|
|
|
- }
|
|
|
-
|
|
|
+ req.OutPacketsMeter.Mark(1)
|
|
|
+ req.OutTrafficMeter.Mark(int64(size))
|
|
|
+ req.ServingTimeMeter.Update(time.Duration(task.servingTime))
|
|
|
+ }
|
|
|
+ }()
|
|
|
// If the client has made too much invalid request(e.g. request a non-existent data),
|
|
|
// reject them to prevent SPAM attack.
|
|
|
if p.getInvalid() > maxRequestErrors {
|