Skip to content

Commit

Permalink
fix: WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
Vadim committed Sep 2, 2024
1 parent 8c44ffa commit 0307706
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 25 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# ratelimiter
# Sliding window log rate limiter

A generic thread-safe rate limiter library for Golang inspired but unsatisfied by https://github.com/Narasimha1997/ratelimiter and https://github.com/uber-go/ratelimit
A thread-safe rate limiter library for Golang inspired but unsatisfied by https://github.com/Narasimha1997/ratelimiter and https://github.com/uber-go/ratelimit

This library can be used in your codebase to rate-limit any API.

### Installation:
The package can be installed as a Go 1.17 module.
The package can be installed as a Go 1.22 module.

```
go get github.com/filinvadim/ratelimiter
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/filinvadim/ratelimiter

go 1.21
go 1.22
68 changes: 47 additions & 21 deletions limiter.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,75 @@ import (
"time"
)

// Limiter Weighted Sliding Window Log.
type Limiter struct {
interval time.Duration
taskNum uint32
limiterLock *sync.Mutex
limit uint32

innerLock *sync.RWMutex
ctx context.Context
stopChan chan struct{}
storedLimit uint32
requests []time.Time
mx *sync.Mutex
ctx context.Context
isClosed *atomic.Bool

Check failure on line 18 in limiter.go

View workflow job for this annotation

GitHub Actions / build

undefined: atomic.Bool
}

func NewLimiter(ctx context.Context, limit uint32, interval time.Duration) *Limiter {
limiter := &Limiter{
limiterLock: new(sync.Mutex),
innerLock: new(sync.RWMutex),
return &Limiter{
interval: interval,
limit: limit,
storedLimit: limit,
requests: make([]time.Time, 0, limit),
mx: new(sync.Mutex),
ctx: ctx,
stopChan: make(chan struct{}),
isClosed: new(atomic.Bool),

Check failure on line 29 in limiter.go

View workflow job for this annotation

GitHub Actions / build

undefined: atomic.Bool
}

return limiter
}

func (l *Limiter) Limit(weight uint32, fn func()) {
l.mx.Lock()
defer l.mx.Unlock()

if weight == 0 {
weight = 1
}
atomic.AddUint32(&l.taskNum, 1*weight)
if atomic.LoadUint32(&l.taskNum) >= atomic.LoadUint32(&l.limit) {
l.limiterLock.Lock()
l.innerLock.RLock()
time.Sleep(l.interval)
l.innerLock.RUnlock()
l.limiterLock.Unlock()
now := time.Now()

cutoff := now.Add(-l.interval)
i := 0
for i < len(l.requests) && l.requests[i].Before(cutoff) {
i++
}
l.requests = l.requests[i:]

if weight <= l.limit {
l.requests = append(l.requests, now)
l.limit -= weight
} else {
timeToWait := l.requests[0].Add(l.interval).Sub(now)
l.limit = l.storedLimit

if l.ctx.Err() != nil {
return
}

time.Sleep(timeToWait) // rate limited here
}
if l.ctx.Err() != nil {
return
}
if l.isClosed.Load() {
return
}

fn()
}

func (l *Limiter) IsLocked() bool {
return atomic.LoadUint32(&l.taskNum) >= atomic.LoadUint32(&l.limit)
l.mx.Lock()
defer l.mx.Unlock()

return l.limit == 0
}

func (l *Limiter) Close() {
close(l.stopChan)
l.isClosed.Store(true)
}

0 comments on commit 0307706

Please sign in to comment.