From 9721fcbac0ecb23cc642f011e2c8e607735d832e Mon Sep 17 00:00:00 2001 From: Evgenii Stratonikov Date: Wed, 26 Feb 2025 13:18:40 +0300 Subject: [PATCH] limiting: Make SemaphoreLimiter.Acquire() zero-alloc Previously, `Acquire` on exising key did 1 allocation because `func() { sem.Release() }` was a closure capturing different variables. Signed-off-by: Evgenii Stratonikov --- limiting/limiter.go | 2 +- limiting/limiter_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/limiting/limiter.go b/limiting/limiter.go index 4d29a71..0f550f8 100644 --- a/limiting/limiter.go +++ b/limiting/limiter.go @@ -69,7 +69,7 @@ func (lr *SemaphoreLimiter) Acquire(key string) (ReleaseFunc, bool) { } if ok := sem.Acquire(); ok { - return func() { sem.Release() }, true + return sem.Release, true } return nil, false } diff --git a/limiting/limiter_test.go b/limiting/limiter_test.go index c6087f1..2dfe6b0 100644 --- a/limiting/limiter_test.go +++ b/limiting/limiter_test.go @@ -136,3 +136,24 @@ func resetFailCounts(testCases []*testCase) { tc.failCount.Store(0) } } + +func TestLimiterZeroAlloc(t *testing.T) { + const key = "key" + + s, err := limiting.NewSemaphoreLimiter([]limiting.KeyLimit{{Keys: []string{key}, Limit: 1}}) + require.NoError(t, err) + + testAllocs := func(t *testing.T, key string) float64 { + return testing.AllocsPerRun(100, func() { + release, ok := s.Acquire(key) + require.True(t, ok) + release() + }) + } + t.Run("existing key", func(t *testing.T) { + require.Zero(t, testAllocs(t, key)) + }) + t.Run("missing key", func(t *testing.T) { + require.Zero(t, testAllocs(t, "missing "+key)) + }) +}