github.com/core-coin/go-core/v2@v2.1.9/les/flowcontrol/control.go (about)

     1  // Copyright 2016 by the Authors
     2  // This file is part of the go-core library.
     3  //
     4  // The go-core library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-core library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  // Package flowcontrol implements a client side flow control mechanism
    18  package flowcontrol
    19  
    20  import (
    21  	"fmt"
    22  	"math"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/core-coin/go-core/v2/common/mclock"
    27  	"github.com/core-coin/go-core/v2/log"
    28  )
    29  
    30  const (
    31  	// fcTimeConst is the time constant applied for MinRecharge during linear
    32  	// buffer recharge period
    33  	fcTimeConst = time.Millisecond
    34  	// DecParamDelay is applied at server side when decreasing capacity in order to
    35  	// avoid a buffer underrun error due to requests sent by the client before
    36  	// receiving the capacity update announcement
    37  	DecParamDelay = time.Second * 2
    38  	// keepLogs is the duration of keeping logs; logging is not used if zero
    39  	keepLogs = 0
    40  )
    41  
    42  // ServerParams are the flow control parameters specified by a server for a client
    43  //
    44  // Note: a server can assign different amounts of capacity to each client by giving
    45  // different parameters to them.
    46  type ServerParams struct {
    47  	BufLimit, MinRecharge uint64
    48  }
    49  
    50  // scheduledUpdate represents a delayed flow control parameter update
    51  type scheduledUpdate struct {
    52  	time   mclock.AbsTime
    53  	params ServerParams
    54  }
    55  
    56  // ClientNode is the flow control system's representation of a client
    57  // (used in server mode only)
    58  type ClientNode struct {
    59  	params         ServerParams
    60  	bufValue       int64
    61  	lastTime       mclock.AbsTime
    62  	updateSchedule []scheduledUpdate
    63  	sumCost        uint64            // sum of req costs received from this client
    64  	accepted       map[uint64]uint64 // value = sumCost after accepting the given req
    65  	connected      bool
    66  	lock           sync.Mutex
    67  	cm             *ClientManager
    68  	log            *logger
    69  	cmNodeFields
    70  }
    71  
    72  // NewClientNode returns a new ClientNode
    73  func NewClientNode(cm *ClientManager, params ServerParams) *ClientNode {
    74  	node := &ClientNode{
    75  		cm:        cm,
    76  		params:    params,
    77  		bufValue:  int64(params.BufLimit),
    78  		lastTime:  cm.clock.Now(),
    79  		accepted:  make(map[uint64]uint64),
    80  		connected: true,
    81  	}
    82  	if keepLogs > 0 {
    83  		node.log = newLogger(keepLogs)
    84  	}
    85  	cm.connect(node)
    86  	return node
    87  }
    88  
    89  // Disconnect should be called when a client is disconnected
    90  func (node *ClientNode) Disconnect() {
    91  	node.lock.Lock()
    92  	defer node.lock.Unlock()
    93  
    94  	node.connected = false
    95  	node.cm.disconnect(node)
    96  }
    97  
    98  // BufferStatus returns the current buffer value and limit
    99  func (node *ClientNode) BufferStatus() (uint64, uint64) {
   100  	node.lock.Lock()
   101  	defer node.lock.Unlock()
   102  
   103  	if !node.connected {
   104  		return 0, 0
   105  	}
   106  	now := node.cm.clock.Now()
   107  	node.update(now)
   108  	node.cm.updateBuffer(node, 0, now)
   109  	bv := node.bufValue
   110  	if bv < 0 {
   111  		bv = 0
   112  	}
   113  	return uint64(bv), node.params.BufLimit
   114  }
   115  
   116  // OneTimeCost subtracts the given amount from the node's buffer.
   117  //
   118  // Note: this call can take the buffer into the negative region internally.
   119  // In this case zero buffer value is returned by exported calls and no requests
   120  // are accepted.
   121  func (node *ClientNode) OneTimeCost(cost uint64) {
   122  	node.lock.Lock()
   123  	defer node.lock.Unlock()
   124  
   125  	now := node.cm.clock.Now()
   126  	node.update(now)
   127  	node.bufValue -= int64(cost)
   128  	node.cm.updateBuffer(node, -int64(cost), now)
   129  }
   130  
   131  // Freeze notifies the client manager about a client freeze event in which case
   132  // the total capacity allowance is slightly reduced.
   133  func (node *ClientNode) Freeze() {
   134  	node.lock.Lock()
   135  	frozenCap := node.params.MinRecharge
   136  	node.lock.Unlock()
   137  	node.cm.reduceTotalCapacity(frozenCap)
   138  }
   139  
   140  // update recalculates the buffer value at a specified time while also performing
   141  // scheduled flow control parameter updates if necessary
   142  func (node *ClientNode) update(now mclock.AbsTime) {
   143  	for len(node.updateSchedule) > 0 && node.updateSchedule[0].time <= now {
   144  		node.recalcBV(node.updateSchedule[0].time)
   145  		node.updateParams(node.updateSchedule[0].params, now)
   146  		node.updateSchedule = node.updateSchedule[1:]
   147  	}
   148  	node.recalcBV(now)
   149  }
   150  
   151  // recalcBV recalculates the buffer value at a specified time
   152  func (node *ClientNode) recalcBV(now mclock.AbsTime) {
   153  	dt := uint64(now - node.lastTime)
   154  	if now < node.lastTime {
   155  		dt = 0
   156  	}
   157  	node.bufValue += int64(node.params.MinRecharge * dt / uint64(fcTimeConst))
   158  	if node.bufValue > int64(node.params.BufLimit) {
   159  		node.bufValue = int64(node.params.BufLimit)
   160  	}
   161  	if node.log != nil {
   162  		node.log.add(now, fmt.Sprintf("updated  bv=%d  MRR=%d  BufLimit=%d", node.bufValue, node.params.MinRecharge, node.params.BufLimit))
   163  	}
   164  	node.lastTime = now
   165  }
   166  
   167  // UpdateParams updates the flow control parameters of a client node
   168  func (node *ClientNode) UpdateParams(params ServerParams) {
   169  	node.lock.Lock()
   170  	defer node.lock.Unlock()
   171  
   172  	now := node.cm.clock.Now()
   173  	node.update(now)
   174  	if params.MinRecharge >= node.params.MinRecharge {
   175  		node.updateSchedule = nil
   176  		node.updateParams(params, now)
   177  	} else {
   178  		for i, s := range node.updateSchedule {
   179  			if params.MinRecharge >= s.params.MinRecharge {
   180  				s.params = params
   181  				node.updateSchedule = node.updateSchedule[:i+1]
   182  				return
   183  			}
   184  		}
   185  		node.updateSchedule = append(node.updateSchedule, scheduledUpdate{time: now + mclock.AbsTime(DecParamDelay), params: params})
   186  	}
   187  }
   188  
   189  // updateParams updates the flow control parameters of the node
   190  func (node *ClientNode) updateParams(params ServerParams, now mclock.AbsTime) {
   191  	diff := int64(params.BufLimit - node.params.BufLimit)
   192  	if diff > 0 {
   193  		node.bufValue += diff
   194  	} else if node.bufValue > int64(params.BufLimit) {
   195  		node.bufValue = int64(params.BufLimit)
   196  	}
   197  	node.cm.updateParams(node, params, now)
   198  }
   199  
   200  // AcceptRequest returns whether a new request can be accepted and the missing
   201  // buffer amount if it was rejected due to a buffer underrun. If accepted, maxCost
   202  // is deducted from the flow control buffer.
   203  func (node *ClientNode) AcceptRequest(reqID, index, maxCost uint64) (accepted bool, bufShort uint64, priority int64) {
   204  	node.lock.Lock()
   205  	defer node.lock.Unlock()
   206  
   207  	now := node.cm.clock.Now()
   208  	node.update(now)
   209  	if int64(maxCost) > node.bufValue {
   210  		if node.log != nil {
   211  			node.log.add(now, fmt.Sprintf("rejected  reqID=%d  bv=%d  maxCost=%d", reqID, node.bufValue, maxCost))
   212  			node.log.dump(now)
   213  		}
   214  		return false, maxCost - uint64(node.bufValue), 0
   215  	}
   216  	node.bufValue -= int64(maxCost)
   217  	node.sumCost += maxCost
   218  	if node.log != nil {
   219  		node.log.add(now, fmt.Sprintf("accepted  reqID=%d  bv=%d  maxCost=%d  sumCost=%d", reqID, node.bufValue, maxCost, node.sumCost))
   220  	}
   221  	node.accepted[index] = node.sumCost
   222  	return true, 0, node.cm.accepted(node, maxCost, now)
   223  }
   224  
   225  // RequestProcessed should be called when the request has been processed
   226  func (node *ClientNode) RequestProcessed(reqID, index, maxCost, realCost uint64) uint64 {
   227  	node.lock.Lock()
   228  	defer node.lock.Unlock()
   229  
   230  	now := node.cm.clock.Now()
   231  	node.update(now)
   232  	node.cm.processed(node, maxCost, realCost, now)
   233  	bv := node.bufValue + int64(node.sumCost-node.accepted[index])
   234  	if node.log != nil {
   235  		node.log.add(now, fmt.Sprintf("processed  reqID=%d  bv=%d  maxCost=%d  realCost=%d  sumCost=%d  oldSumCost=%d  reportedBV=%d", reqID, node.bufValue, maxCost, realCost, node.sumCost, node.accepted[index], bv))
   236  	}
   237  	delete(node.accepted, index)
   238  	if bv < 0 {
   239  		return 0
   240  	}
   241  	return uint64(bv)
   242  }
   243  
   244  // ServerNode is the flow control system's representation of a server
   245  // (used in client mode only)
   246  type ServerNode struct {
   247  	clock       mclock.Clock
   248  	bufEstimate uint64
   249  	bufRecharge bool
   250  	lastTime    mclock.AbsTime
   251  	params      ServerParams
   252  	sumCost     uint64            // sum of req costs sent to this server
   253  	pending     map[uint64]uint64 // value = sumCost after sending the given req
   254  	log         *logger
   255  	lock        sync.RWMutex
   256  }
   257  
   258  // NewServerNode returns a new ServerNode
   259  func NewServerNode(params ServerParams, clock mclock.Clock) *ServerNode {
   260  	node := &ServerNode{
   261  		clock:       clock,
   262  		bufEstimate: params.BufLimit,
   263  		bufRecharge: false,
   264  		lastTime:    clock.Now(),
   265  		params:      params,
   266  		pending:     make(map[uint64]uint64),
   267  	}
   268  	if keepLogs > 0 {
   269  		node.log = newLogger(keepLogs)
   270  	}
   271  	return node
   272  }
   273  
   274  // UpdateParams updates the flow control parameters of the node
   275  func (node *ServerNode) UpdateParams(params ServerParams) {
   276  	node.lock.Lock()
   277  	defer node.lock.Unlock()
   278  
   279  	node.recalcBLE(mclock.Now())
   280  	if params.BufLimit > node.params.BufLimit {
   281  		node.bufEstimate += params.BufLimit - node.params.BufLimit
   282  	} else {
   283  		if node.bufEstimate > params.BufLimit {
   284  			node.bufEstimate = params.BufLimit
   285  		}
   286  	}
   287  	node.params = params
   288  }
   289  
   290  // recalcBLE recalculates the lowest estimate for the client's buffer value at
   291  // the given server at the specified time
   292  func (node *ServerNode) recalcBLE(now mclock.AbsTime) {
   293  	if now < node.lastTime {
   294  		return
   295  	}
   296  	if node.bufRecharge {
   297  		dt := uint64(now - node.lastTime)
   298  		node.bufEstimate += node.params.MinRecharge * dt / uint64(fcTimeConst)
   299  		if node.bufEstimate >= node.params.BufLimit {
   300  			node.bufEstimate = node.params.BufLimit
   301  			node.bufRecharge = false
   302  		}
   303  	}
   304  	node.lastTime = now
   305  	if node.log != nil {
   306  		node.log.add(now, fmt.Sprintf("updated  bufEst=%d  MRR=%d  BufLimit=%d", node.bufEstimate, node.params.MinRecharge, node.params.BufLimit))
   307  	}
   308  }
   309  
   310  // safetyMargin is added to the flow control waiting time when estimated buffer value is low
   311  const safetyMargin = time.Millisecond
   312  
   313  // CanSend returns the minimum waiting time required before sending a request
   314  // with the given maximum estimated cost. Second return value is the relative
   315  // estimated buffer level after sending the request (divided by BufLimit).
   316  func (node *ServerNode) CanSend(maxCost uint64) (time.Duration, float64) {
   317  	node.lock.RLock()
   318  	defer node.lock.RUnlock()
   319  
   320  	if node.params.BufLimit == 0 {
   321  		return time.Duration(math.MaxInt64), 0
   322  	}
   323  	now := node.clock.Now()
   324  	node.recalcBLE(now)
   325  	maxCost += uint64(safetyMargin) * node.params.MinRecharge / uint64(fcTimeConst)
   326  	if maxCost > node.params.BufLimit {
   327  		maxCost = node.params.BufLimit
   328  	}
   329  	if node.bufEstimate >= maxCost {
   330  		relBuf := float64(node.bufEstimate-maxCost) / float64(node.params.BufLimit)
   331  		if node.log != nil {
   332  			node.log.add(now, fmt.Sprintf("canSend  bufEst=%d  maxCost=%d  true  relBuf=%f", node.bufEstimate, maxCost, relBuf))
   333  		}
   334  		return 0, relBuf
   335  	}
   336  	timeLeft := time.Duration((maxCost - node.bufEstimate) * uint64(fcTimeConst) / node.params.MinRecharge)
   337  	if node.log != nil {
   338  		node.log.add(now, fmt.Sprintf("canSend  bufEst=%d  maxCost=%d  false  timeLeft=%v", node.bufEstimate, maxCost, timeLeft))
   339  	}
   340  	return timeLeft, 0
   341  }
   342  
   343  // QueuedRequest should be called when the request has been assigned to the given
   344  // server node, before putting it in the send queue. It is mandatory that requests
   345  // are sent in the same order as the QueuedRequest calls are made.
   346  func (node *ServerNode) QueuedRequest(reqID, maxCost uint64) {
   347  	node.lock.Lock()
   348  	defer node.lock.Unlock()
   349  
   350  	now := node.clock.Now()
   351  	node.recalcBLE(now)
   352  	// Note: we do not know when requests actually arrive to the server so bufRecharge
   353  	// is not turned on here if buffer was full; in this case it is going to be turned
   354  	// on by the first reply's bufValue feedback
   355  	if node.bufEstimate >= maxCost {
   356  		node.bufEstimate -= maxCost
   357  	} else {
   358  		log.Error("Queued request with insufficient buffer estimate")
   359  		node.bufEstimate = 0
   360  	}
   361  	node.sumCost += maxCost
   362  	node.pending[reqID] = node.sumCost
   363  	if node.log != nil {
   364  		node.log.add(now, fmt.Sprintf("queued  reqID=%d  bufEst=%d  maxCost=%d  sumCost=%d", reqID, node.bufEstimate, maxCost, node.sumCost))
   365  	}
   366  }
   367  
   368  // ReceivedReply adjusts estimated buffer value according to the value included in
   369  // the latest request reply.
   370  func (node *ServerNode) ReceivedReply(reqID, bv uint64) {
   371  	node.lock.Lock()
   372  	defer node.lock.Unlock()
   373  
   374  	now := node.clock.Now()
   375  	node.recalcBLE(now)
   376  	if bv > node.params.BufLimit {
   377  		bv = node.params.BufLimit
   378  	}
   379  	sc, ok := node.pending[reqID]
   380  	if !ok {
   381  		return
   382  	}
   383  	delete(node.pending, reqID)
   384  	cc := node.sumCost - sc
   385  	newEstimate := uint64(0)
   386  	if bv > cc {
   387  		newEstimate = bv - cc
   388  	}
   389  	if newEstimate > node.bufEstimate {
   390  		// Note: we never reduce the buffer estimate based on the reported value because
   391  		// this can only happen because of the delayed delivery of the latest reply.
   392  		// The lowest estimate based on the previous reply can still be considered valid.
   393  		node.bufEstimate = newEstimate
   394  	}
   395  
   396  	node.bufRecharge = node.bufEstimate < node.params.BufLimit
   397  	node.lastTime = now
   398  	if node.log != nil {
   399  		node.log.add(now, fmt.Sprintf("received  reqID=%d  bufEst=%d  reportedBv=%d  sumCost=%d  oldSumCost=%d", reqID, node.bufEstimate, bv, node.sumCost, sc))
   400  	}
   401  }
   402  
   403  // ResumeFreeze cleans all pending requests and sets the buffer estimate to the
   404  // reported value after resuming from a frozen state
   405  func (node *ServerNode) ResumeFreeze(bv uint64) {
   406  	node.lock.Lock()
   407  	defer node.lock.Unlock()
   408  
   409  	for reqID := range node.pending {
   410  		delete(node.pending, reqID)
   411  	}
   412  	now := node.clock.Now()
   413  	node.recalcBLE(now)
   414  	if bv > node.params.BufLimit {
   415  		bv = node.params.BufLimit
   416  	}
   417  	node.bufEstimate = bv
   418  	node.bufRecharge = node.bufEstimate < node.params.BufLimit
   419  	node.lastTime = now
   420  	if node.log != nil {
   421  		node.log.add(now, fmt.Sprintf("unfreeze  bv=%d  sumCost=%d", bv, node.sumCost))
   422  	}
   423  }
   424  
   425  // DumpLogs dumps the event log if logging is used
   426  func (node *ServerNode) DumpLogs() {
   427  	node.lock.Lock()
   428  	defer node.lock.Unlock()
   429  
   430  	if node.log != nil {
   431  		node.log.dump(node.clock.Now())
   432  	}
   433  }