Files
automana/client/ratelimit.go

92 lines
1.7 KiB
Go
Raw Normal View History

2021-09-26 03:41:40 +00:00
package client
2021-09-28 04:43:38 +00:00
import "net/http"
import "strconv"
2021-09-26 03:41:40 +00:00
import "sync"
import "time"
type RateLimit struct {
perSecond float64
balance float64
limit float64
updated time.Time
mu sync.Mutex
}
func NewRateLimit(perSecond, limit float64) *RateLimit {
return &RateLimit{
perSecond: perSecond,
balance: limit,
limit: limit,
updated: time.Now(),
}
}
func NewRateLimitPerMinute(perMinute, limit float64) *RateLimit {
return NewRateLimit(perMinute/60, limit)
}
// Acquire sufficient rate quota to execute 1 operation
func (rl *RateLimit) Acquire1() {
rl.AcquireN(1.0)
}
// Acquire sufficient rate quota to execute /cost/ operations
func (rl *RateLimit) AcquireN(cost float64) {
for {
rl.mu.Lock()
rl.replenish()
if rl.balance >= cost {
rl.balance -= cost
rl.mu.Unlock()
return
}
costDelta := cost - rl.balance
sleep := time.Duration(costDelta / rl.perSecond * float64(time.Second))
rl.mu.Unlock()
time.Sleep(sleep)
}
}
2021-09-28 04:43:38 +00:00
func (rl *RateLimit) MaybeRetryAfter(resp *http.Response) error {
header := resp.Header.Get("Retry-After")
if header == "" {
return nil
}
retryAfter, err := strconv.ParseInt(header, 10, 64)
if err != nil {
return err
}
rl.RetryAfter(retryAfter)
return nil
}
2021-09-26 03:41:40 +00:00
func (rl *RateLimit) RetryAfter(seconds int64) {
rl.mu.Lock()
defer rl.mu.Unlock()
target := 1.0 - (float64(seconds) * rl.perSecond)
if target < rl.balance {
rl.balance = target
}
}
// Must be called with rl.mu already locked
func (rl *RateLimit) replenish() {
now := time.Now()
timeDelta := now.Sub(rl.updated)
balanceDelta := timeDelta.Seconds() * rl.perSecond
rl.balance += balanceDelta
if rl.balance > rl.limit {
rl.balance = rl.limit
}
rl.updated = now
}