Home > front end >  Unable to grasp my head around go rate package
Unable to grasp my head around go rate package

Time:12-20

I need to rate-limit the requests to an API and I'm considering using the native golang.org/x/time/rate package for that purpose. In order to fiddling a little bit with its API and make sure that my assumptions are correct, I've created this tests, but it definitely seems that I'm missing something here:

package main

import (
    "github.com/stretchr/testify/require"
    "golang.org/x/time/rate"
    "sync"
    "testing"
)

func TestLimiter(t *testing.T) {
    limiter := rate.NewLimiter(rate.Limit(5),1)
    wg := sync.WaitGroup{}
    successful := 0

    for i:=1; i<=10; i   {
        wg.Add(1)
        go func() {
            defer wg.Done()
            if limiter.Allow() {
                successful  
            }
        }()
    }
    wg.Wait()

    require.Equal(t, 5, successful)
    
    // This test fails with
    // Expected :5
    // Actual   :1
}

Could someone explain me why is this? Shouldn't the rate limiter allow 5 req/s?

CodePudding user response:

First, you have a data race. Multiple goroutines write successful without synchronization: undefined behavior.

You may use the sync/atomic package for an easy and safe counting:

limiter := rate.NewLimiter(rate.Limit(5), 1)
wg := sync.WaitGroup{}
successful := int32(0)

for i := 1; i <= 10; i   {
    wg.Add(1)
    go func() {
        defer wg.Done()
        if limiter.Allow() {
            atomic.AddInt32(&successful, 1)
        }
    }()
}
wg.Wait()

fmt.Println(successful)

This will output:

1

Why? Because you allow 5 events per second, that's 1 event per 0.2 seconds. Launching 10 goroutines and checking will take less than 0.2 seconds, so only one event will be allowed.

If you add 200 ms sleep in the loop, then all will be allowed and output will be 10:

for i := 1; i <= 10; i   {
    time.Sleep(200 * time.Millisecond)
    wg.Add(1)
    go func() {
        defer wg.Done()
        if limiter.Allow() {
            atomic.AddInt32(&successful, 1)
        }
    }()
}

If you add 100 ms sleep, then on average half of them will be allowed and output will be 5.

What you want probably is allow a burst of 5 with 5 events/sec:

limiter := rate.NewLimiter(rate.Limit(5), 5)

Using this limiter without sleep, you'll also get an output of 5. That's because 5 events are allowed without hitting the rate limit, and without sleep the rest will not be allowed.

  • Related