Skip to content

Commit 8063244

Browse files
authored
Merge pull request #3 from augustus281/DEV
[algo]: leaky bucket
2 parents ff649b4 + 5a29971 commit 8063244

File tree

6 files changed

+116
-2
lines changed

6 files changed

+116
-2
lines changed

go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
module github.com/augustus281/ratelimiter.git
22

33
go 1.22.2
4+
5+
require (
6+
github.com/edwingeng/deque v1.0.3 // indirect
7+
github.com/gammazero/deque v0.2.1 // indirect
8+
)

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
github.com/edwingeng/deque v1.0.3 h1:gx+5OnQK8qMcYNUxcD/M76BT/LujVNVAZEP/MGF8WNw=
2+
github.com/edwingeng/deque v1.0.3/go.mod h1:3Ys1pJhyVaB6iWigv4o2r6Ug1GZmfDWqvqmO6bjojg0=
3+
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
4+
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=

leakybucket.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package ratelimiter
2+
3+
import (
4+
"sync"
5+
"time"
6+
)
7+
8+
type LeakyBucket struct {
9+
capacity int // maximum number of requests in the bucket
10+
leakRate int // rate at which requests leak (requests/second)
11+
bucket []time.Time // hold request timestamps
12+
lastLeak time.Time // last time we leaked from the bucket
13+
sync.Mutex
14+
}
15+
16+
func NewLeakyBucket(capacity, leakRate int) *LeakyBucket {
17+
return &LeakyBucket{
18+
capacity: capacity,
19+
leakRate: leakRate,
20+
bucket: []time.Time{},
21+
lastLeak: time.Now(),
22+
}
23+
}
24+
25+
func (lb *LeakyBucket) AllowRequest() bool {
26+
now := time.Now()
27+
leakedTime := now.Sub(lb.lastLeak).Seconds()
28+
29+
leaked := int(leakedTime) * lb.leakRate
30+
if leaked > 0 {
31+
if leaked > len(lb.bucket) {
32+
lb.bucket = []time.Time{}
33+
} else {
34+
lb.bucket = lb.bucket[leaked:]
35+
}
36+
lb.lastLeak = now
37+
}
38+
39+
if len(lb.bucket) < lb.capacity {
40+
lb.bucket = append(lb.bucket, now)
41+
return true
42+
}
43+
44+
return false
45+
}

leakybucket_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package ratelimiter
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestLeakyBucket(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
capacity int
12+
leakRate int
13+
initRequests int
14+
sleepDuration time.Duration
15+
expectedResult bool
16+
}{
17+
{
18+
name: "Allow request when bucket is not full",
19+
capacity: 5,
20+
leakRate: 1,
21+
initRequests: 4,
22+
sleepDuration: 0,
23+
expectedResult: true,
24+
},
25+
{
26+
name: "Deny request when bucket is full",
27+
capacity: 5,
28+
leakRate: 1,
29+
initRequests: 5,
30+
sleepDuration: 0,
31+
expectedResult: false,
32+
},
33+
{
34+
name: "Allow request after bucket leaks",
35+
capacity: 5,
36+
leakRate: 1,
37+
initRequests: 5,
38+
sleepDuration: 2 * time.Second,
39+
expectedResult: true,
40+
},
41+
}
42+
43+
for _, tt := range tests {
44+
t.Run(tt.name, func(t *testing.T) {
45+
lb := NewLeakyBucket(tt.capacity, tt.leakRate)
46+
47+
for i := 0; i < tt.initRequests; i++ {
48+
lb.AllowRequest()
49+
}
50+
51+
if tt.sleepDuration > 0 {
52+
time.Sleep(tt.sleepDuration)
53+
}
54+
55+
if lb.AllowRequest() != tt.expectedResult {
56+
t.Errorf("AllowRequest() = %v, want %v", lb.AllowRequest(), tt.expectedResult)
57+
}
58+
})
59+
}
60+
}

tokenbucket.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tokenbucket
1+
package ratelimiter
22

33
import (
44
"sync"

tokenbucket_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package tokenbucket
1+
package ratelimiter
22

33
import (
44
"sync"

0 commit comments

Comments
 (0)