github.com/andy2046/gopie@v0.7.0/pkg/ratelimit/ratelimit.go (about) 1 // Package ratelimit implements a rate limiter. 2 package ratelimit 3 4 import ( 5 "fmt" 6 "sync" 7 "time" 8 ) 9 10 // Limit defines the maximum number of requests per second. 11 type Limit float64 12 13 // Every converts a time interval between requests to a Limit. 14 func Every(interval time.Duration) Limit { 15 if interval <= 0 { 16 panic("ratelimit: invalid time interval for Every") 17 } 18 return 1 / Limit(interval.Seconds()) 19 } 20 21 // Limiter implements a token bucket limiter at rate `r` tokens per second with burst size of `b` tokens. 22 type Limiter struct { 23 limit Limit 24 burst int 25 mu sync.Mutex 26 tokens float64 27 // last is the last time the limiter's tokens got updated 28 last time.Time 29 // lastRequest is the latest time of a request 30 lastRequest time.Time 31 } 32 33 // Limit returns the Limiter's rate. 34 func (l *Limiter) Limit() Limit { 35 return l.limit 36 } 37 38 // Burst returns the Limiter's burst size. 39 func (l *Limiter) Burst() int { 40 return l.burst 41 } 42 43 // New returns a new Limiter at rate `r` tokens per second with burst of `b` tokens. 44 func New(r Limit, b int) *Limiter { 45 return &Limiter{ 46 limit: r, 47 burst: b, 48 } 49 } 50 51 // Allow is the shortcut for AllowN(time.Now(), 1). 52 func (l *Limiter) Allow() bool { 53 return l.AllowN(time.Now(), 1) 54 } 55 56 // AllowN checks whether `n` requests may happen at time `now`. 57 func (l *Limiter) AllowN(now time.Time, n int) bool { 58 return l.reserveN(now, n, 0).ok 59 } 60 61 // Wait is the shortcut for WaitN(time.Now(), 1). 62 func (l *Limiter) Wait() (time.Duration, error) { 63 return l.WaitN(time.Now(), 1) 64 } 65 66 // WaitN calculates the time duration to wait before `n` requests may happen at time `now`. 67 func (l *Limiter) WaitN(now time.Time, n int) (time.Duration, error) { 68 return l.waitN(now, n) 69 } 70 71 func (l *Limiter) waitN(now time.Time, n int) (time.Duration, error) { 72 if n > l.burst { 73 return 0, fmt.Errorf("ratelimit: WaitN %d exceeds limiter's burst %d", n, l.burst) 74 } 75 76 _, _, tokens := l.adjust(now) 77 78 // calculate the remaining number of tokens resulting from the request. 79 tokens -= float64(n) 80 81 var waitDuration time.Duration 82 if tokens < 0 { 83 waitDuration = l.limit.durationFromTokens(-tokens) 84 } 85 86 maxWaitDuration := l.limit.durationFromTokens(float64(n)) 87 ok := n <= l.burst && waitDuration <= maxWaitDuration 88 89 if ok { 90 return waitDuration, nil 91 } 92 return 0, fmt.Errorf("ratelimit: WaitN %d exceeds maximum wait duration", n) 93 } 94 95 type reservation struct { 96 ok bool 97 tokens int 98 timeToAct time.Time 99 } 100 101 func (l *Limiter) reserveN(now time.Time, n int, maxWaitDuration time.Duration) reservation { 102 l.mu.Lock() 103 104 noow, last, tokens := l.adjust(now) 105 106 // calculate the remaining number of tokens resulting from the request. 107 tokens -= float64(n) 108 109 var waitDuration time.Duration 110 if tokens < 0 { 111 waitDuration = l.limit.durationFromTokens(-tokens) 112 } 113 114 ok := n <= l.burst && waitDuration <= maxWaitDuration 115 116 r := reservation{ 117 ok: ok, 118 } 119 120 if ok { 121 r.tokens = n 122 r.timeToAct = noow.Add(waitDuration) 123 l.last = noow 124 l.tokens = tokens 125 l.lastRequest = r.timeToAct 126 } else { 127 l.last = last 128 } 129 130 l.mu.Unlock() 131 return r 132 } 133 134 // adjust calculates the updated state for Limiter resulting from the passage of time. 135 func (l *Limiter) adjust(now time.Time) (newNow, newLast time.Time, newTokens float64) { 136 last := l.last 137 if now.Before(last) { 138 last = now 139 } 140 141 maxElapsed := l.limit.durationFromTokens(float64(l.burst) - l.tokens) 142 elapsed := now.Sub(last) 143 if elapsed > maxElapsed { 144 elapsed = maxElapsed 145 } 146 147 delta := l.limit.tokensFromDuration(elapsed) 148 tokens := l.tokens + delta 149 if burst := float64(l.burst); tokens > burst { 150 tokens = burst 151 } 152 153 return now, last, tokens 154 } 155 156 func (lmt Limit) durationFromTokens(tokens float64) time.Duration { 157 seconds := tokens / float64(lmt) 158 return time.Nanosecond * time.Duration(1e9*seconds) 159 } 160 161 func (lmt Limit) tokensFromDuration(d time.Duration) float64 { 162 return d.Seconds() * float64(lmt) 163 }