Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package throttle

// NewLimiter exposes newLimiter to tests.
var NewLimiter = newLimiter
18 changes: 15 additions & 3 deletions throttle.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
t.cfg.InitialRate = t.cfg.MaxRate
}

t.rl = rate.NewLimiter(t.cfg.InitialRate, 1)
t.rl = newLimiter(t.cfg.InitialRate)
}

if t.cfg.LimitLatency != 0 {
Expand Down Expand Up @@ -347,6 +347,18 @@
}
}

// burstWindow is the duration of rate limiter burst, it has to cover timer wake-up lag.
//
// With burst of 1, any late wake-up of a waiting goroutine loses the time exceeding one token, so on hosts with
// coarse timers (about 1ms is common on busy VMs) throughput is bounded by roughly 2/lag, e.g. 2000 per second,
// whatever the rate limit is. Burst of limit*lag lets the limiter catch up.
const burstWindow = 5 * time.Millisecond

// newLimiter creates a rate limiter allowing short bursts, at least 1.
func newLimiter(lim rate.Limit) *rate.Limiter {
return rate.NewLimiter(lim, max(1, int(float64(lim)*burstWindow.Seconds())))
}

// block removes delta slots from the active pool, negative delta returns slots back, t.adj must be held.
func (t *throttle) block(delta int64) {
for i := int64(0); i < delta; i++ {
Expand Down Expand Up @@ -413,7 +425,7 @@
t.cfg.MinRate, t.cfg.MaxRate = l.MinRate, l.MaxRate

if cur := t.rl.Limit(); cur > l.MaxRate || cur < l.MinRate {
t.rl = rate.NewLimiter(min(max(cur, l.MinRate), l.MaxRate), 1)
t.rl = newLimiter(min(max(cur, l.MinRate), l.MaxRate))

Check notice on line 428 in throttle.go

View workflow job for this annotation

GitHub Actions / test (stable)

1 statement(s) on lines 428:429 are not covered by tests.
}
}

Expand Down Expand Up @@ -460,7 +472,7 @@
}

if newLim != lim {
t.rl = rate.NewLimiter(newLim, 1)
t.rl = newLimiter(newLim)
}

return newLim
Expand Down
7 changes: 7 additions & 0 deletions throttle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,10 @@ func TestThrottle_SetLimits(t *testing.T) {
require.NoError(t, th.SetLimits(l))
assert.Eventually(t, func() bool { return th.Concurrency() == 6 }, time.Second, time.Millisecond)
}

func TestNewLimiter_burst(t *testing.T) {
assert.Equal(t, 1, throttle.NewLimiter(1).Burst())
assert.Equal(t, 1, throttle.NewLimiter(100).Burst())
assert.Equal(t, 25, throttle.NewLimiter(5000).Burst())
assert.Equal(t, 250, throttle.NewLimiter(50000).Burst())
}
Loading