Skip to content

Commit b6ac324

Browse files
committed
per key default limiter
1 parent 5ee7d65 commit b6ac324

2 files changed

Lines changed: 610 additions & 0 deletions

File tree

efficient_ratelimit.go

Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
package ratelimit
2+
3+
import (
4+
"context"
5+
"errors"
6+
"sync"
7+
"time"
8+
)
9+
10+
var (
11+
ErrAutoKeyAlreadyExists = errors.New("key already exists")
12+
ErrAutoKeyMissing = errors.New("key does not exist")
13+
)
14+
15+
// AutoLimiterOption is a function that configures the AutoLimiter
16+
type AutoLimiterOption func(*AutoLimiter)
17+
18+
// WithUnlimited sets the limiter to unlimited mode
19+
func WithUnlimited() AutoLimiterOption {
20+
return func(e *AutoLimiter) {
21+
e.defaultOptions.IsUnlimited = true
22+
}
23+
}
24+
25+
// WithDuration sets the duration for the rate limiter
26+
func WithDuration(duration time.Duration) AutoLimiterOption {
27+
return func(e *AutoLimiter) {
28+
e.defaultOptions.Duration = duration
29+
}
30+
}
31+
32+
// WithMaxCount sets the maximum count for the rate limiter
33+
func WithMaxCount(maxCount uint) AutoLimiterOption {
34+
return func(e *AutoLimiter) {
35+
e.defaultOptions.MaxCount = maxCount
36+
}
37+
}
38+
39+
// AutoLimiter is an improved version of MultiLimiter with better memory management
40+
type AutoLimiter struct {
41+
limiters sync.Map // map of active limiters
42+
options sync.Map // map of custom options (only for keys with custom settings)
43+
ctx context.Context
44+
45+
// Default options for automatically created limiters
46+
defaultOptions *internalOptions
47+
}
48+
49+
// NewAutoLimiter creates a new auto limiter instance using functional options
50+
func NewAutoLimiter(ctx context.Context, opts ...AutoLimiterOption) *AutoLimiter {
51+
e := &AutoLimiter{
52+
ctx: ctx,
53+
limiters: sync.Map{},
54+
options: sync.Map{},
55+
defaultOptions: &internalOptions{},
56+
}
57+
58+
// Apply all options to set the defaults
59+
for _, opt := range opts {
60+
opt(e)
61+
}
62+
63+
return e
64+
}
65+
66+
// internalOptions holds configuration internally (not exposed to users)
67+
type internalOptions struct {
68+
Key string
69+
IsUnlimited bool
70+
MaxCount uint
71+
Duration time.Duration
72+
}
73+
74+
// Validate internal options
75+
func (o *internalOptions) Validate() error {
76+
if !o.IsUnlimited {
77+
if o.Key == "" {
78+
return errors.New("empty keys not allowed")
79+
}
80+
if o.MaxCount == 0 {
81+
return errors.New("maxcount cannot be zero")
82+
}
83+
if o.Duration == 0 {
84+
return errors.New("time duration not set")
85+
}
86+
}
87+
return nil
88+
}
89+
90+
// Add creates a new rate limiter with custom settings (only for keys that need specific limits)
91+
func (e *AutoLimiter) Add(key string, opts ...AutoLimiterOption) error {
92+
// Create options struct and apply functional options to it
93+
options := &internalOptions{Key: key}
94+
95+
// Apply all options to the options struct
96+
for _, opt := range opts {
97+
// Create a temporary limiter to apply the option
98+
tempLimiter := &AutoLimiter{defaultOptions: options}
99+
opt(tempLimiter)
100+
}
101+
102+
// Validate the configuration
103+
if !options.IsUnlimited {
104+
if key == "" {
105+
return errors.New("empty keys not allowed")
106+
}
107+
if options.MaxCount == 0 {
108+
return errors.New("maxcount cannot be zero")
109+
}
110+
if options.Duration == 0 {
111+
return errors.New("time duration not set")
112+
}
113+
}
114+
115+
// Check if key already exists
116+
if _, exists := e.limiters.Load(key); exists {
117+
return ErrAutoKeyAlreadyExists
118+
}
119+
120+
// Create new limiter with custom settings
121+
var rlimiter *Limiter
122+
if options.IsUnlimited {
123+
rlimiter = NewUnlimited(e.ctx)
124+
} else {
125+
rlimiter = New(e.ctx, options.MaxCount, options.Duration)
126+
}
127+
128+
// Store the limiter and options
129+
e.limiters.Store(key, rlimiter)
130+
e.options.Store(key, options)
131+
132+
return nil
133+
}
134+
135+
// Take one token from bucket - creates limiter automatically if it doesn't exist
136+
func (e *AutoLimiter) Take(key string) error {
137+
limiter, err := e.get(key)
138+
if err != nil {
139+
// Key doesn't exist, create it with default settings
140+
limiter = e.createOrDefault(key)
141+
}
142+
limiter.Take()
143+
return nil
144+
}
145+
146+
// CanTake checks if the rate limiter with the given key has any token
147+
func (e *AutoLimiter) CanTake(key string) bool {
148+
limiter, err := e.get(key)
149+
if err != nil {
150+
// Key doesn't exist, create it with default settings
151+
limiter = e.createOrDefault(key)
152+
}
153+
return limiter.CanTake()
154+
}
155+
156+
// Allow checks if a request is allowed - creates limiter automatically if it doesn't exist
157+
func (e *AutoLimiter) Allow(key string) bool {
158+
limiter, err := e.get(key)
159+
if err != nil {
160+
// Key doesn't exist, create it with default settings
161+
limiter = e.createOrDefault(key)
162+
}
163+
return limiter.CanTake()
164+
}
165+
166+
// AllowN checks if N requests are allowed - creates limiter automatically if it doesn't exist
167+
func (e *AutoLimiter) AllowN(key string, n int) bool {
168+
limiter, err := e.get(key)
169+
if err != nil {
170+
// Key doesn't exist, create it with default settings
171+
limiter = e.createOrDefault(key)
172+
}
173+
174+
// Check if we can take N tokens
175+
for i := 0; i < n; i++ {
176+
if !limiter.CanTake() {
177+
return false
178+
}
179+
}
180+
return true
181+
}
182+
183+
// Stop internal limiters with defined keys or all if no key is provided
184+
func (e *AutoLimiter) Stop(keys ...string) {
185+
if len(keys) == 0 {
186+
e.limiters.Range(func(key, value any) bool {
187+
if limiter, ok := value.(*Limiter); ok {
188+
limiter.Stop()
189+
e.limiters.Delete(key)
190+
// Keep the options for potential recreation
191+
}
192+
return true
193+
})
194+
return
195+
}
196+
for _, v := range keys {
197+
if limiter, err := e.get(v); err == nil {
198+
limiter.Stop()
199+
e.limiters.Delete(v)
200+
// Keep the options for potential recreation
201+
}
202+
}
203+
}
204+
205+
// Remove completely removes a key and its options
206+
func (e *AutoLimiter) Remove(key string) {
207+
// Stop and remove the limiter if it exists
208+
if limiter, err := e.get(key); err == nil {
209+
limiter.Stop()
210+
e.limiters.Delete(key)
211+
}
212+
// Remove the stored options
213+
e.options.Delete(key)
214+
}
215+
216+
// get returns *Limiter instance
217+
func (e *AutoLimiter) get(key string) (*Limiter, error) {
218+
val, _ := e.limiters.Load(key)
219+
if val == nil {
220+
return nil, ErrAutoKeyMissing
221+
}
222+
if limiter, ok := val.(*Limiter); ok {
223+
return limiter, nil
224+
}
225+
return nil, errors.New("type assertion of rateLimiter failed in autoLimiter")
226+
}
227+
228+
// recreateLimiter recreates a limiter from stored options
229+
func (e *AutoLimiter) recreateLimiter(key string) (*Limiter, error) {
230+
// Check if we have stored options for this key
231+
optsVal, exists := e.options.Load(key)
232+
if !exists {
233+
return nil, ErrAutoKeyMissing
234+
}
235+
236+
opts, ok := optsVal.(*internalOptions)
237+
if !ok {
238+
return nil, errors.New("invalid options type")
239+
}
240+
241+
// Create new limiter with stored options
242+
var rlimiter *Limiter
243+
if opts.IsUnlimited {
244+
rlimiter = NewUnlimited(e.ctx)
245+
} else {
246+
rlimiter = New(e.ctx, opts.MaxCount, opts.Duration)
247+
}
248+
249+
// Store the new limiter
250+
e.limiters.Store(key, rlimiter)
251+
252+
return rlimiter, nil
253+
}
254+
255+
// createOrDefault creates a new limiter with default settings
256+
func (e *AutoLimiter) createOrDefault(key string) *Limiter {
257+
// First check if we have stored custom options for this key
258+
if optsVal, exists := e.options.Load(key); exists {
259+
if _, ok := optsVal.(*internalOptions); ok {
260+
// Recreate with stored custom options
261+
if limiter, err := e.recreateLimiter(key); err == nil {
262+
return limiter
263+
}
264+
// If recreation fails, fall back to defaults
265+
}
266+
}
267+
268+
// No custom options, create with stored default options
269+
var limiter *Limiter
270+
if e.defaultOptions.IsUnlimited {
271+
limiter = NewUnlimited(e.ctx)
272+
} else {
273+
limiter = New(e.ctx, e.defaultOptions.MaxCount, e.defaultOptions.Duration)
274+
}
275+
276+
// Store the limiter
277+
e.limiters.Store(key, limiter)
278+
279+
// Note: We don't store options for default limiters since they can be recreated
280+
// Only custom limiters (added via Add()) get their options stored
281+
282+
return limiter
283+
}
284+
285+
// AddAndTake adds a key with custom settings if not present and then takes a token
286+
func (e *AutoLimiter) AddAndTake(key string, opts ...AutoLimiterOption) error {
287+
// Check if limiter already exists
288+
if limiter, err := e.get(key); err == nil {
289+
limiter.Take()
290+
return nil
291+
}
292+
293+
// Add the key with custom settings
294+
if err := e.Add(key, opts...); err != nil {
295+
return err
296+
}
297+
298+
// Take a token
299+
return e.Take(key)
300+
}

0 commit comments

Comments
 (0)