I've been trying to make a rate limiter for my application and came across this code. After reading it I still have some trouble understanding what it does exactly.
My current understanding:
1) SetSmallRateLimit and SetLongRateLimit are called to initialize the channels and starts running the handlers in a goroutine.
2) When requestAndUnmarshal is called, checkRateLimiter sends a signal to the queue channel.
What I don't understand:
1) RateLimitHandler sleeps the duration of time.After(pertime) and afterwards clears the queue channel. Not sure what triggerWatcher and returnChan are doing.
2) checkTimeTrigger - Don't understand what this function is doing or its purpose.
var (
smallRateChan rateChan
longRateChan rateChan
)
type rateChan struct {
RateQueue chan bool
TriggerChan chan bool
}
//10 requests every 10 seconds
func SetSmallRateLimit(numrequests int, pertime time.Duration) {
smallRateChan = rateChan{
RateQueue: make(chan bool, numrequests),
TriggerChan: make(chan bool),
}
go rateLimitHandler(smallRateChan, pertime)
}
//500 requests every 10 minutes
func SetLongRateLimit(numrequests int, pertime time.Duration) {
longRateChan = rateChan{
RateQueue: make(chan bool, numrequests),
TriggerChan: make(chan bool),
}
go rateLimitHandler(longRateChan, pertime)
}
func rateLimitHandler(RateChan rateChan, pertime time.Duration) {
returnChan := make(chan bool)
go timeTriggerWatcher(RateChan.TriggerChan, returnChan)
for {
<-returnChan
<-time.After(pertime)
go timeTriggerWatcher(RateChan.TriggerChan, returnChan)
length := len(RateChan.RateQueue)
for i := 0; i < length; i++ {
<-RateChan.RateQueue
}
}
}
func timeTriggerWatcher(timeTrigger chan bool, returnChan chan bool) {
timeTrigger <- true
returnChan <- true
}
func requestAndUnmarshal(requestURL string, v interface{}) (err error) {
checkRateLimiter(smallRateChan)
checkRateLimiter(longRateChan)
resp, err := http.Get(requestURL)
defer resp.Body.Close()
if err != nil {
return
}
checkTimeTrigger(smallRateChan)
checkTimeTrigger(longRateChan)
if resp.StatusCode != http.StatusOK {
return RiotError{StatusCode: resp.StatusCode}
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
err = json.Unmarshal(body, v)
if err != nil {
return
}
return
}
func checkRateLimiter(RateChan rateChan) {
if RateChan.RateQueue != nil && RateChan.TriggerChan != nil {
RateChan.RateQueue <- true
}
}
func checkTimeTrigger(RateChan rateChan) {
if RateChan.RateQueue != nil && RateChan.TriggerChan != nil {
select {
case <-RateChan.TriggerChan:
default:
}
}
}
I don't think you should use this code to learn anything useful. I'm not sure but it seems to try to limit the request rate but it's wrong. It allows to make certain amount of request and then wait for a time interval. After the time interval it allows you to make requests again. All of that is done in a very sophisticated way.
But it can lead to very strange scenarios. Let's say you make 1req/h and your limit is 500req/20sec. Then this code would cause you to wait 20sec after 500 hours and allow to make requests again.
checkTimeTrigger
removes a message from RateChan.TriggerChan if it has any and does nothing if it has not and returns immediately.
This code isn't obviously DRY. Better use https://godoc.org/golang.org/x/time/rate is you want to limit your request rate.