Lock-free rate-limit algorithm + bug-fix (#2694)

This commit is contained in:
Aditya Manthramurthy 2016-09-14 11:27:37 -07:00 committed by Harshavardhana
parent da9ae574df
commit 6533927237

View File

@ -19,7 +19,6 @@ package cmd
import ( import (
"errors" "errors"
"net/http" "net/http"
"sync"
) )
var errTooManyRequests = errors.New("Too many clients in the waiting list") var errTooManyRequests = errors.New("Too many clients in the waiting list")
@ -28,7 +27,6 @@ var errTooManyRequests = errors.New("Too many clients in the waiting list")
// limit the number of concurrent http requests. // limit the number of concurrent http requests.
type rateLimit struct { type rateLimit struct {
handler http.Handler handler http.Handler
lock sync.Mutex
workQueue chan struct{} workQueue chan struct{}
waitQueue chan struct{} waitQueue chan struct{}
} }
@ -37,26 +35,26 @@ type rateLimit struct {
// channel this is in-turn used to rate limit incoming connections in // channel this is in-turn used to rate limit incoming connections in
// ServeHTTP() http.Handler method. // ServeHTTP() http.Handler method.
func (c *rateLimit) acquire() error { func (c *rateLimit) acquire() error {
//lock access to enter waitQueue //attempt to enter the waitQueue. If no slot is immediately
c.lock.Lock() //available return error.
// Kick out clients when it is really crowded select {
if len(c.waitQueue) == cap(c.waitQueue) { case c.waitQueue <- struct{}{}:
defer c.lock.Unlock() //unlock after return //entered wait queue
break
default:
//no slot available for waiting
return errTooManyRequests return errTooManyRequests
} }
// Add new element in waitQueue to keep track of clients //block attempting to enter the workQueue. If the workQueue is
// wanting to process their requests //full, there can be at most cap(waitQueue) == 4*globalMaxConn
c.waitQueue <- struct{}{} //goroutines waiting here because of the select above.
select {
// Unlock now. If we unlock before sending to the waitQueue case c.workQueue <- struct{}{}:
// channel, we can have multiple go-routines blocked on //entered workQueue - so remove one waiter. This step
// sending to the waitQueue (and exceeding the max. number of //does not block as the waitQueue cannot be empty.
// waiting connections.) <-c.waitQueue
c.lock.Unlock() }
// Block to put a waiting go-routine into processing mode.
c.workQueue <- <-c.waitQueue
return nil return nil
} }