goconcurrencychannelroutines

Using rate.NewLimiter rate limiter with channels


I'm using a rate limiter to throttle the number of requests that are routed

The requests are sent to a channel, and I want to limit the number that are processed per second but i'm struggling to understand if i'm setting this correctly, I don't get an error, but i'm unsure if i'm even using the rate limiter

This is what is being added to the channel:

type processItem struct {
    itemString string
}

Here's the channel and limiter:

itemChannel := make(chan processItem, 5)
itemThrottler := rate.NewLimiter(4, 1) //4 a second, no more per second (1)
var waitGroup sync.WaitGroup

Items are added to the channel:

case "newItem":
    waitGroup.Add(1)
    itemToExec := new(processItem)
    itemToExec.itemString = "item string"
    itemChannel <- *itemToExec

Then a go routine is used to process everything that is added to the channel:

go func() {
    defer waitGroup.Done()
    err := itemThrottler.Wait(context.Background())
    if err != nil {
        fmt.Printf("Error with limiter: %s", err)
        return
    }
    for item := range itemChannel {
        execItem(item.itemString) // the processing function
    }
    defer func() { <-itemChannel }()
}()
waitGroup.Wait()

Can someone confirm that the following occurs:

I don't understand what "err := itemThrottler.Wait(context.Background())" is doing in the code, how is this being invoked?


Solution

  • ... i'm unsure if i'm even using the rate limiter

    Yes, you are using the rate-limiter. You are rate-limiting the case "newItem": branch of your code.

    I don't understand what "err := itemThrottler.Wait(context.Background())" is doing in the code

    itemThrottler.Wait(..) will just stagger requests (4/s i.e. every 0.25s) - it does not refuse requests if the rate is exceeded. So what does this mean? If you receive a glut of 1000 requests in 1 second:

    The 996 will unblock at a rate of 4/s and thus the backlog of pending go-routines will not clear for another 4 minutes (or maybe longer if more requests come in). A backlog of go-routines may or may not be what you want. If not, you may want to use Limiter.Allow - and if false is returned, then refuse the request (i.e. don't create a go-routine) and return a 429 error (if this is a HTTP request).

    Finally, if this is a HTTP request, you should use it's imbedded context when calling Wait e.g.

    func (a *app) myHandler(w http.ResponseWriter, r *http.Request) {
        // ...
    
        err := a.ratelimiter(r.Context())
    
        if err != nil {
            // client http request most likely canceled (i.e. caller disconnected)
        }
    }