A Transparent and Verifiable Way to Sync the AT Protocol's PLC Directory
1package plc
2
3import (
4 "context"
5 "time"
6)
7
8// RateLimiter implements a token bucket rate limiter
9type RateLimiter struct {
10 tokens chan struct{}
11 refillRate time.Duration
12 maxTokens int
13 stopRefill chan struct{}
14}
15
16// NewRateLimiter creates a new rate limiter
17// Example: NewRateLimiter(90, time.Minute) = 90 requests per minute
18func NewRateLimiter(requestsPerPeriod int, period time.Duration) *RateLimiter {
19 rl := &RateLimiter{
20 tokens: make(chan struct{}, requestsPerPeriod),
21 refillRate: period / time.Duration(requestsPerPeriod),
22 maxTokens: requestsPerPeriod,
23 stopRefill: make(chan struct{}),
24 }
25
26 // Fill initially
27 for i := 0; i < requestsPerPeriod; i++ {
28 rl.tokens <- struct{}{}
29 }
30
31 // Start refill goroutine
32 go rl.refill()
33
34 return rl
35}
36
37// refill adds tokens at the specified rate
38func (rl *RateLimiter) refill() {
39 ticker := time.NewTicker(rl.refillRate)
40 defer ticker.Stop()
41
42 for {
43 select {
44 case <-ticker.C:
45 select {
46 case rl.tokens <- struct{}{}:
47 // Token added
48 default:
49 // Buffer full, skip
50 }
51 case <-rl.stopRefill:
52 return
53 }
54 }
55}
56
57// Wait blocks until a token is available or context is cancelled
58func (rl *RateLimiter) Wait(ctx context.Context) error {
59 select {
60 case <-rl.tokens:
61 return nil
62 case <-ctx.Done():
63 return ctx.Err()
64 }
65}
66
67// Stop stops the rate limiter and cleans up resources
68func (rl *RateLimiter) Stop() {
69 close(rl.stopRefill)
70}