github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/vent/chain/ethereum/throttler.go (about)

     1  package ethereum
     2  
     3  import (
     4  	"math/big"
     5  	"time"
     6  
     7  	"github.com/hyperledger/burrow/logging"
     8  )
     9  
    10  type Throttler struct {
    11  	// Request timestamps as unix nanos (avoid space overhead of time.Time)
    12  	requests                 []int64
    13  	maxRequestsPerNanosecond *big.Float
    14  	// Window over which to accumulate request times
    15  	window time.Duration
    16  	logger *logging.Logger
    17  }
    18  
    19  func NewThrottler(maxRequests int, timeBase time.Duration, window time.Duration, logger *logging.Logger) *Throttler {
    20  	maxRequestsPerNanosecond := new(big.Float).SetInt64(int64(maxRequests))
    21  	maxRequestsPerNanosecond.Quo(maxRequestsPerNanosecond, new(big.Float).SetInt64(int64(timeBase)))
    22  	return &Throttler{
    23  		maxRequestsPerNanosecond: maxRequestsPerNanosecond,
    24  		window:                   window,
    25  		logger:                   logger,
    26  	}
    27  }
    28  
    29  func (t *Throttler) Throttle() {
    30  	time.Sleep(t.calculateWait())
    31  }
    32  
    33  func (t *Throttler) calculateWait() time.Duration {
    34  	requests := len(t.requests)
    35  	if requests < 2 {
    36  		return 0
    37  	}
    38  	delta := t.requests[requests-1] - t.requests[0]
    39  	deltaNanoseconds := new(big.Float).SetInt64(delta)
    40  
    41  	allowedRequestsInDelta := new(big.Float).Mul(deltaNanoseconds, t.maxRequestsPerNanosecond)
    42  
    43  	overage := allowedRequestsInDelta.Sub(new(big.Float).SetInt64(int64(requests)), allowedRequestsInDelta)
    44  	if overage.Sign() > 0 {
    45  		// Wait just long enough to eat our overage at max request rate
    46  		nanos, _ := new(big.Float).Quo(overage, t.maxRequestsPerNanosecond).Int64()
    47  		wait := time.Duration(nanos)
    48  		t.logger.InfoMsg("throttling connection",
    49  			"num_requests", requests,
    50  			"over_period", time.Duration(delta).String(),
    51  			"requests_overage", overage.String(),
    52  			"wait", wait.String(),
    53  		)
    54  		return wait
    55  	}
    56  	return 0
    57  }
    58  
    59  func (t *Throttler) addNow() {
    60  	t.add(time.Now())
    61  }
    62  
    63  func (t *Throttler) add(now time.Time) {
    64  	cutoff := now.Add(-t.window)
    65  	// Remove expired requests
    66  	for len(t.requests) > 0 && t.requests[0] < cutoff.UnixNano() {
    67  		t.requests = t.requests[1:]
    68  	}
    69  	t.requests = append(t.requests, now.UnixNano())
    70  }