A Transparent and Verifiable Way to Sync the AT Protocol's PLC Directory
at test-validate 70 lines 1.4 kB view raw
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}