Skip to content

Commit 7559a91

Browse files
author
augustus
committed
[algo]: token bucket
1 parent 901840a commit 7559a91

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Ratelimiter
2+
This is Go implement algorithms about ratelimiter.
3+
4+
### Installation:
5+
The package can be installed as a Go module.
6+
7+
```
8+
go get github.com/augustus281/ratelimiter
9+
```

tokenbucket.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package tokenbucket
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
type TokenBucket struct {
9+
sync.Mutex
10+
tokens int // Current token count, start with a full bucket
11+
maxTokens int // Maximum number of tokens the bucket hold
12+
refillRate int // Rate at which tokens are added (tokens/second)
13+
lastRefillTime time.Time // Last time we checked the token count
14+
}
15+
16+
func NewTokenBucket(maxTokens, refillRate int) *TokenBucket {
17+
return &TokenBucket{
18+
tokens: maxTokens,
19+
maxTokens: maxTokens,
20+
refillRate: refillRate,
21+
lastRefillTime: time.Now(),
22+
}
23+
}
24+
25+
func (tb *TokenBucket) AddToken(tokens int) bool {
26+
tb.Lock()
27+
defer tb.Unlock()
28+
29+
tb.refill()
30+
if tokens < tb.tokens {
31+
tb.tokens -= tokens
32+
return true
33+
}
34+
35+
return false
36+
}
37+
38+
func (tb *TokenBucket) refill() {
39+
now := time.Now()
40+
duration := time.Since(tb.lastRefillTime)
41+
tokenAdd := tb.tokens * int(duration.Seconds())
42+
tb.tokens = tb.min(tb.maxTokens, tb.tokens+tokenAdd)
43+
tb.lastRefillTime = now
44+
}
45+
46+
func (tb *TokenBucket) min(a, b int) int {
47+
if a <= b {
48+
return a
49+
}
50+
return b
51+
}

tokenbucket_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package tokenbucket
2+
3+
import (
4+
"sync"
5+
"testing"
6+
"time"
7+
)
8+
9+
func TestTokenBucket_AddToken(t *testing.T) {
10+
type fiels struct {
11+
token int
12+
maxToken int
13+
refillRate int
14+
lastRefillTime time.Time
15+
Mutex sync.Mutex
16+
}
17+
18+
tests := []struct {
19+
name string
20+
fiels fiels
21+
want bool
22+
takedToken int
23+
expectedToken int
24+
}{
25+
{
26+
"token available now",
27+
fiels{
28+
token: 10,
29+
maxToken: 10,
30+
refillRate: 1,
31+
lastRefillTime: time.Now(),
32+
},
33+
true,
34+
9,
35+
1,
36+
},
37+
{
38+
"no token available now",
39+
fiels{
40+
token: 0,
41+
maxToken: 10,
42+
refillRate: 1,
43+
lastRefillTime: time.Now(),
44+
},
45+
false,
46+
10,
47+
0,
48+
},
49+
{
50+
"tokens available after adjustment",
51+
fiels{
52+
token: 1,
53+
maxToken: 10,
54+
refillRate: 1,
55+
lastRefillTime: time.Now().Add(-1 * time.Second),
56+
},
57+
true,
58+
1,
59+
1,
60+
},
61+
{
62+
"tokens do not refresh above capacity",
63+
fiels{
64+
token: 10,
65+
maxToken: 10,
66+
refillRate: 1,
67+
lastRefillTime: time.Now().Add(-2 * time.Minute),
68+
},
69+
true,
70+
1,
71+
9,
72+
},
73+
{
74+
"refreshs 4 tokens",
75+
fiels{
76+
token: 4,
77+
maxToken: 10,
78+
refillRate: 1,
79+
lastRefillTime: time.Now().Add(-5 * time.Second),
80+
},
81+
true,
82+
4,
83+
6,
84+
},
85+
}
86+
87+
for _, tt := range tests {
88+
t.Run(tt.name, func(t *testing.T) {
89+
b := &TokenBucket{
90+
tokens: tt.fiels.token,
91+
maxTokens: tt.fiels.maxToken,
92+
refillRate: tt.fiels.refillRate,
93+
lastRefillTime: tt.fiels.lastRefillTime,
94+
Mutex: tt.fiels.Mutex,
95+
}
96+
97+
if got := b.AddToken(tt.takedToken); got != tt.want {
98+
t.Errorf("TokenBucket.Add(%v) = %v, want %v", tt.takedToken, got, tt.want)
99+
}
100+
101+
if count := b.tokens; count != tt.expectedToken {
102+
t.Errorf("Token count incorrect. Got %v, want %v", count, tt.expectedToken)
103+
}
104+
105+
if b.tokens > b.maxTokens {
106+
t.Errorf("Max token is %v but current count is %v", b.maxTokens, b.tokens)
107+
}
108+
})
109+
}
110+
}

0 commit comments

Comments
 (0)