-
Notifications
You must be signed in to change notification settings - Fork 303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update the Limiter interface to accept a context.Context object #11
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,8 @@ import ( | |
"sync" | ||
"time" | ||
|
||
"go.uber.org/ratelimit/internal/clock" | ||
"context" | ||
"math" | ||
) | ||
|
||
// Note: This file is inspired by: | ||
|
@@ -35,24 +36,16 @@ import ( | |
// may block to throttle the goroutine. | ||
type Limiter interface { | ||
// Take should block to make sure that the RPS is met. | ||
Take() time.Time | ||
} | ||
|
||
// Clock is the minimum necessary interface to instantiate a rate limiter with | ||
// a clock or mock clock, compatible with clocks created using | ||
// github.com/andres-erbsen/clock. | ||
type Clock interface { | ||
Now() time.Time | ||
Sleep(time.Duration) | ||
Take(context.Context) bool | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right now, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we update the documentation to make that part of the API I'm also not sure how valuable it is, since the caller can just check What is the value in having this in the API? |
||
} | ||
|
||
type limiter struct { | ||
sync.Mutex | ||
last time.Time | ||
timer *time.Timer | ||
sleepFor time.Duration | ||
perRequest time.Duration | ||
maxSlack time.Duration | ||
clock Clock | ||
} | ||
|
||
// Option configures a Limiter. | ||
|
@@ -63,24 +56,14 @@ func New(rate int, opts ...Option) Limiter { | |
l := &limiter{ | ||
perRequest: time.Second / time.Duration(rate), | ||
maxSlack: -10 * time.Second / time.Duration(rate), | ||
timer: time.NewTimer(time.Duration(math.MaxInt64)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rather than use a max duration, should we create a timer, stop it (maybe in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can also leave the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
for _, opt := range opts { | ||
opt(l) | ||
} | ||
if l.clock == nil { | ||
l.clock = clock.New() | ||
} | ||
return l | ||
} | ||
|
||
// WithClock returns an option for ratelimit.New that provides an alternate | ||
// Clock implementation, typically a mock Clock for testing. | ||
func WithClock(clock Clock) Option { | ||
return func(l *limiter) { | ||
l.clock = clock | ||
} | ||
} | ||
|
||
// WithoutSlack is an option for ratelimit.New that initializes the limiter | ||
// without any initial tolerance for bursts of traffic. | ||
var WithoutSlack Option = withoutSlackOption | ||
|
@@ -91,23 +74,24 @@ func withoutSlackOption(l *limiter) { | |
|
||
// Take blocks to ensure that the time spent between multiple | ||
// Take calls is on average time.Second/rate. | ||
func (t *limiter) Take() time.Time { | ||
func (t *limiter) Take(ctx context.Context) bool { | ||
t.Lock() | ||
defer t.Unlock() | ||
|
||
now := t.clock.Now() | ||
now := time.Now() | ||
|
||
// If this is our first request, then we allow it. | ||
if t.last.IsZero() { | ||
t.last = now | ||
return t.last | ||
return true | ||
} | ||
|
||
// sleepFor calculates how much time we should sleep based on | ||
// the perRequest budget and how long the last request took. | ||
// Since the request may take longer than the budget, this number | ||
// can get negative, and is summed across requests. | ||
t.sleepFor += t.perRequest - now.Sub(t.last) | ||
t.last = now | ||
|
||
// We shouldn't allow sleepFor to get too negative, since it would mean that | ||
// a service that slowed down a lot for a short period of time would get | ||
|
@@ -118,14 +102,21 @@ func (t *limiter) Take() time.Time { | |
|
||
// If sleepFor is positive, then we should sleep now. | ||
if t.sleepFor > 0 { | ||
t.clock.Sleep(t.sleepFor) | ||
if !t.timer.Stop() { | ||
<-t.timer.C | ||
} | ||
t.timer.Reset(t.sleepFor) | ||
select { | ||
case <-t.timer.C: | ||
case <-ctx.Done(): | ||
return false | ||
} | ||
|
||
t.last = now.Add(t.sleepFor) | ||
t.sleepFor = 0 | ||
} else { | ||
t.last = now | ||
} | ||
|
||
return t.last | ||
return true | ||
} | ||
|
||
type unlimited struct{} | ||
|
@@ -135,6 +126,6 @@ func NewUnlimited() Limiter { | |
return unlimited{} | ||
} | ||
|
||
func (unlimited) Take() time.Time { | ||
return time.Now() | ||
func (unlimited) Take(_ context.Context) bool { | ||
return true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no blank between stdlib imports
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still see a blank between "time" and "context"