Skip to content

Commit e7e2c64

Browse files
committed
feat: send bucket update when rate limit applied
1 parent e68fcdb commit e7e2c64

2 files changed

Lines changed: 57 additions & 8 deletions

File tree

waku/v2/api/publish/rln_rate_limiting.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111

1212
var ErrRateLimited = errors.New("rate limit exceeded")
1313

14-
const RlnLimiterCapacity = 100
14+
const RlnLimiterCapacity = 600
1515
const RlnLimiterRefillInterval = 10 * time.Minute
1616

1717
// RlnRateLimiter is used to rate limit the outgoing messages,
@@ -22,15 +22,23 @@ type RlnRateLimiter struct {
2222
tokens int
2323
refillInterval time.Duration
2424
lastRefill time.Time
25+
updateCh chan BucketUpdate
26+
}
27+
28+
// BucketUpdate includes the information that need to be persisted in database.
29+
type BucketUpdate struct {
30+
RemainingTokens int
31+
LastRefill time.Time
2532
}
2633

2734
// NewRlnPublishRateLimiter creates a new rate limiter, starts with a full capacity bucket.
28-
func NewRlnRateLimiter(capacity int, refillInterval time.Duration) *RlnRateLimiter {
35+
func NewRlnRateLimiter(capacity int, refillInterval time.Duration, availableTokens int, lastRefill time.Time, updateCh chan BucketUpdate) *RlnRateLimiter {
2936
return &RlnRateLimiter{
3037
capacity: capacity,
31-
tokens: capacity, // Start with a full bucket
38+
tokens: availableTokens, // Start with a full bucket in the first run, then track the remaining tokens in storage
3239
refillInterval: refillInterval,
33-
lastRefill: time.Now(),
40+
lastRefill: lastRefill,
41+
updateCh: updateCh,
3442
}
3543
}
3644

@@ -42,19 +50,26 @@ func (rl *RlnRateLimiter) Allow() bool {
4250
// Refill tokens if the refill interval has passed
4351
now := time.Now()
4452
if now.Sub(rl.lastRefill) >= rl.refillInterval {
45-
rl.tokens = rl.capacity // Refill the bucket
53+
rl.tokens = rl.capacity
4654
rl.lastRefill = now
55+
rl.sendUpdate()
4756
}
4857

4958
// Check if there are tokens available
5059
if rl.tokens > 0 {
5160
rl.tokens--
61+
rl.sendUpdate()
5262
return true
5363
}
5464

5565
return false
5666
}
5767

68+
// sendUpdate sends the latest token state to the update channel.
69+
func (rl *RlnRateLimiter) sendUpdate() {
70+
rl.updateCh <- BucketUpdate{RemainingTokens: rl.tokens, LastRefill: rl.lastRefill}
71+
}
72+
5873
func (rl *RlnRateLimiter) Check(ctx context.Context, logger *zap.Logger) error {
5974
if rl.Allow() {
6075
return nil

waku/v2/api/publish/rln_rate_limiting_test.go

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package publish
22

33
import (
44
"context"
5+
"sync"
56
"testing"
67
"time"
78

@@ -10,17 +11,50 @@ import (
1011
)
1112

1213
func TestRlnRateLimit(t *testing.T) {
13-
r := NewRlnRateLimiter(3, 5*time.Second)
14+
updateCh := make(chan BucketUpdate, 10)
15+
refillTime := time.Now()
16+
capacity := 3
17+
r := NewRlnRateLimiter(capacity, 5*time.Second, capacity, refillTime, updateCh)
1418
l := utils.Logger()
1519

16-
for i := 0; i < 3; i++ {
20+
ctx, cancel := context.WithCancel(context.Background())
21+
defer cancel()
22+
23+
sleepDuration := 6 * time.Second
24+
var mu sync.Mutex
25+
go func(ctx context.Context, ch chan BucketUpdate) {
26+
usedToken := 0
27+
for {
28+
select {
29+
case update := <-ch:
30+
mu.Lock()
31+
if update.LastRefill != refillTime {
32+
usedToken = 0
33+
require.WithinDuration(t, refillTime.Add(sleepDuration), update.LastRefill, time.Second, "Last refill timestamp is incorrect")
34+
require.Equal(t, update.RemainingTokens, capacity)
35+
continue
36+
}
37+
usedToken++
38+
require.Equal(t, update.RemainingTokens, capacity-usedToken)
39+
mu.Unlock()
40+
case <-ctx.Done():
41+
return
42+
}
43+
}
44+
}(ctx, updateCh)
45+
46+
for i := 0; i < capacity; i++ {
1747
require.NoError(t, r.Check(context.Background(), l))
1848
}
1949
require.ErrorIs(t, r.Check(context.Background(), l), ErrRateLimited)
2050

21-
time.Sleep(6 * time.Second)
51+
time.Sleep(sleepDuration)
52+
2253
for i := 0; i < 3; i++ {
2354
require.NoError(t, r.Check(context.Background(), l))
2455
}
2556
require.ErrorIs(t, r.Check(context.Background(), l), ErrRateLimited)
57+
58+
// wait for goroutine to finish
59+
time.Sleep(time.Second)
2660
}

0 commit comments

Comments
 (0)