diff --git a/ratelimit/README.md b/ratelimit/README.md index 3c5e84f..4b12b2c 100644 --- a/ratelimit/README.md +++ b/ratelimit/README.md @@ -7,10 +7,15 @@ func NewLimiter(maxCount int, interval time.Duration) *Limiter func (l *Limiter) Acquire(ctx context.Context) error + +func (l *Limiter) Stop() ``` `Limiter` должен гарантировать, что на любом интервале времени `interval`, не больше `maxCount` вызовов `Acquire` могут завершиться без ошибки. Каждый вызов `Acquire` должен либо завершаться успешно, либо завершаться с ошибкой в случае если `ctx` отменили -во время ожидания. +во время ожидания. Об отмене `ctx` нужно нужно узнавать по закрытию канала `ctx.Done()`. Если `ctx` отменён, +нужно возвращать ошибку `ctx.Err()`. + +Вызовы `Acquire()` после `Stop()`, должны сразу завершаться с ошибкой ErrStopped. diff --git a/ratelimit/ratelimit.go b/ratelimit/ratelimit.go index 9641aa2..3695c3d 100644 --- a/ratelimit/ratelimit.go +++ b/ratelimit/ratelimit.go @@ -4,6 +4,7 @@ package ratelimit import ( "context" + "errors" "time" ) @@ -11,6 +12,8 @@ import ( type Limiter struct { } +var ErrStopped = errors.New("limiter stopped") + // NewLimiter returns limiter that throttles rate of successful Acquire() calls // to maxSize events at any given interval. func NewLimiter(maxCount int, interval time.Duration) *Limiter { @@ -20,3 +23,7 @@ func NewLimiter(maxCount int, interval time.Duration) *Limiter { func (l *Limiter) Acquire(ctx context.Context) error { panic("not implemented") } + +func (l *Limiter) Stop() { + panic("not implemented") +} diff --git a/ratelimit/ratelimit_test.go b/ratelimit/ratelimit_test.go index 3af4c27..15c595f 100644 --- a/ratelimit/ratelimit_test.go +++ b/ratelimit/ratelimit_test.go @@ -9,11 +9,15 @@ import ( "time" "github.com/stretchr/testify/require" + "go.uber.org/goleak" "golang.org/x/sync/errgroup" ) func TestNoRateLimit(t *testing.T) { + defer goleak.VerifyNone(t) + limit := NewLimiter(1, 0) + defer limit.Stop() ctx := context.Background() @@ -22,7 +26,12 @@ func TestNoRateLimit(t *testing.T) { } func TestBlockedRateLimit(t *testing.T) { - limit := NewLimiter(0, time.Minute) + defer goleak.VerifyNone(t) + + limit := NewLimiter(1, time.Minute) + defer limit.Stop() + + require.NoError(t, limit.Acquire(context.Background())) ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) defer cancel() @@ -32,7 +41,10 @@ func TestBlockedRateLimit(t *testing.T) { } func TestSimpleLimitCancel(t *testing.T) { + defer goleak.VerifyNone(t) + limit := NewLimiter(1, time.Minute) + defer limit.Stop() ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond) defer cancel() @@ -44,7 +56,10 @@ func TestSimpleLimitCancel(t *testing.T) { } func TestTimeDistribution(t *testing.T) { + defer goleak.VerifyNone(t) + limit := NewLimiter(100, time.Second) + defer limit.Stop() var lock sync.Mutex okTimes := []time.Duration{} @@ -94,12 +109,15 @@ func TestTimeDistribution(t *testing.T) { } func TestStressBlocking(t *testing.T) { + defer goleak.VerifyNone(t) + const ( N = 100 G = 100 ) limit := NewLimiter(N, time.Millisecond*10) + defer limit.Stop() var eg errgroup.Group for i := 0; i < G; i++ { @@ -118,12 +136,15 @@ func TestStressBlocking(t *testing.T) { } func TestStressNoBlocking(t *testing.T) { + defer goleak.VerifyNone(t) + const ( N = 100 G = 100 ) limit := NewLimiter(N, time.Millisecond*10) + defer limit.Stop() var eg errgroup.Group for i := 0; i < G; i++ { @@ -148,6 +169,7 @@ func BenchmarkNoBlocking(b *testing.B) { b.SetBytes(1) limit := NewLimiter(1, 0) + defer limit.Stop() ctx := context.Background()