mennanov / limiters Goto Github PK
View Code? Open in Web Editor NEWGolang rate limiters for distributed applications
Home Page: https://godoc.org/github.com/mennanov/limiters
License: MIT License
Golang rate limiters for distributed applications
Home Page: https://godoc.org/github.com/mennanov/limiters
License: MIT License
After the fix from #22 I found a bug:
With a small amount of Takes from the bucket, where Backend is a Redis with raceCheck=true
then sometimes the expected version is higher than the actual one.
To replicate the issue I created a simple test:
func (s *LimitersTestSuite) TestTokenBucketRealClockRaceCheckRedisProblem() {
ttl := time.Second
limit := int64(2)
refillEvery := time.Duration(int64(ttl) / limit)
locker := l.NewLockNoop()
backend := l.NewTokenBucketRedis(s.redisClient, uuid.New().String(), ttl, true)
clk := l.NewSystemClock()
ctx := context.Background()
bucket := l.NewTokenBucket(
limit,
refillEvery,
locker,
backend,
clk,
nil,
)
for i := 0; i < 4; i++ {
wait, err := bucket.Limit(ctx)
if err != nil {
assert.ErrorIs(s.T(), err, l.ErrLimitExhausted)
}
if wait.Seconds() > 0 {
clk.Sleep(time.Second) // after a second all keys expired and the initial state was returned, but lastVersion is not cleared
}
}
}
checkResponseFromRedis
returns error:
got [1 1 OK OK] from redis, expected [3 1 OK OK]
Hello. Is there any particular reason why locker's Lock
is context aware but Unlock
is not?
Line 14 in db05502
I am implementing redis locker which needs to be context aware during Lock
and Unlock
.
$> go get github.com/mennanov/[email protected]
go: github.com/mennanov/[email protected]: invalid version: module contains a go.mod file, so module path must match major version ("github.com/mennanov/limiters/v2")
$> go get github.com/mennanov/limiters/v2
go: module github.com/mennanov/limiters@upgrade found (v1.1.0), but does not contain package github.com/mennanov/limiters/v2
Given the following:
The TokenBucket limiter should have added 2 tokens to the bucket, but only one token is added.
Relevent Code
now := t.clock.Now().UnixNano()
// Refill the bucket.
tokensToAdd := (now - state.Last) / int64(t.refillRate)
if tokensToAdd > 0 {
state.Last = now
if tokensToAdd+state.Available <= t.capacity {
state.Available += tokensToAdd
} else {
state.Available = t.capacity
}
}
The way this currently works is:
A simple fix would be to modify the logic to truncate state.Last
to t.refillRate
:
now := t.clock.Now().UnixNano()
// Refill the bucket.
tokensToAdd := (now - state.Last) / int64(t.refillRate)
modTime := (now - state.Last) % int64(t.refillRate)
if tokensToAdd > 0 {
if tokensToAdd+state.Available < t.capacity {
state.Available += tokensToAdd
state.Last = now - modTime
} else {
state.Available = t.capacity
state.Last = now
}
}
With that change, a token is correctly added to the bucket for request 3:
Alternatively, the Available
member of the TokenBucketState
struct can be changed from an int64 to a float64 to account for partial tokens, but that would be a more involved change.
Set 60s/10 requests, but if there are many concurrent requests at the same time, all requests will pass, so limiting the rate will become meaningless
Hello,
It does not appear that this package allows for the reset of a limit, is that correct? I have come across a use case where rate limits need to be reset. A user forgets their password, gets rate limited logging into their account. They successfully reset their password, the previous limit should be cleared so they are allowed to attempt to login again.
Would you consider a PR for this feature?
I advise to use multi module to solve this issue
Hi,
Could you please explain what should be ideal value of epsilon in slidingWindow
.
Here is my current configuration
slidingLimiter := registry.GetOrCreate(ip, func() interface{} {
return limiters.NewSlidingWindow(100, window, limiters.NewSlidingWindowRedis(redisClient, ip), clock, 60)
}, 0*time.Second, clock.Now())
t, err := slidingLimiter.(*limiters.SlidingWindow).Limit(req.Context())
fmt.Println("timeDuration: ", t)
if err == limiters.ErrLimitExhausted {
wrt.WriteHeader(http.StatusTooManyRequests)
return
} else if err != nil {
// The limiter failed. This error should be logged and examined.
wrt.WriteHeader(http.StatusInternalServerError)
return
}
serveWebSocket(wrt, req)
When i run a load test,
Sometimes i get this panic
And other times, server breaks, but gives no error, only go routine ids
Please help
This is a corner case for the issue addressed here: #29.
This occurs when the version fetched by State()
is deleted before SetState()
is executed. That will cause an error: got [1 1 OK OK] from redis, expected [<version_before_remove>+1 1 OK OK]
raised by checkResponseFromRedis
.
Potential solution is to always expect version to be in initial state: #42
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.