HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Rate limiter utility class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
rateclassutilitylimiter

Problem

I had a problem in my system where the class that sends messages to other systems sometimes gets flooded and so I needed to add rate limiting. I threw together something that would work, but I would really like to see what I can do to improve and/or clean it up.

This class does not do the actual rate limiting, it only provides timing guidelines on when the rate limited class should perform its next action.

package edu.stsci.util

import scala.collection.mutable
/**
* Used to rate limit a repeated action based on three parameters. This class doesn't
* actually control the action, but tells the calling class how long to wait before
* performing the action again by calling
nextWait. Caller also indicates every time
* an action is performed using
execute.
*
* @param minSpacing The minimum amount of time between actions for burst throughput (in ms)
* @param maxPer The number of actions that can happen during any one
movingPeriod time window
* @param movingPeriod The length of the moving time window for controlling sustained throughput (in ms)
*/
class RateLimiter(val minSpacing: Int, val maxPer: Int, val movingPeriod: Int) {
val periodRate: Double = maxPer / RateLimiter.SUBWINDOW_COUNT.toDouble
val periodInterval = movingPeriod / RateLimiter.SUBWINDOW_COUNT
val sustainedInterval: Long = movingPeriod / maxPer

var last: Long = 0

var totalCount = 0

var movingData = new mutable.Queue[Int]()
var currentCount = 0
var currentEnd = 0l

/**
*
execute` must be called every time an action occurs to update last
* and statistics
*/
def execute() = {
val now = System.currentTimeMillis()
last = now

if (currentEnd == 0l) {
initializeWindows()
}
else {
while (now > currentEnd) {
dropOnePeriod()
queueLastPeriod()
}

currentCount += 1
totalCount += 1
}
}

def initializeWindows(): Unit = {
currentEnd = System.currentTimeMillis() + periodInterval
c

Solution

Slow tests

The tests run slow because of the Thread.sleep calls.
It would be better to inject a "time provider" in the implementation,
so that you can fake System.currentTimeMillis() and Thread.sleep(next) and keep the tests fast.

Cause and effect

The connection between the numbers used in the fixture setup step and in the assertions is not obvious.
Take for example the assertion elapsed should be > 80l in test("burst case").
It's not obvious where 80 comes from and how it's related to the fixture setup.
The consequence is that if something changes in the implementation,
it won't be obvious how to change the test.

I'm guessing these are the lines that contribute to the threshold of 80:

val testObject = new RateLimiter(20, 100, 5000)

    for (...) {
      // ...

      for (count  80l


I'm guessing that elapsed should be > 20 * 4.
It would be good to make the connection obvious by using more local variables with descriptive names, for example:

val minSpacing = 20
    val testObject = new RateLimiter(minSpacing, 100, 5000)

    for (...) {
      // ...

      val samples = 4
      for (count  (minSpacing * samples).toLong


Cosmetic issues

A minor thing, zero is redundant here:

val zero: Long = testObject.nextWait()
  zero should be (0l)


Instead of this:

println("..., wait ", next)


I'm guessing you meant to write this:

println("... , wait " + next)

Code Snippets

val testObject = new RateLimiter(20, 100, 5000)

    for (...) {
      // ...

      for (count <- 1 to 4) {
        // ...
      }

      // ...
      elapsed should be > 80l
val minSpacing = 20
    val testObject = new RateLimiter(minSpacing, 100, 5000)

    for (...) {
      // ...

      val samples = 4
      for (count <- 1 to samples) {
        // ...
      }

      // ...
      elapsed should be > (minSpacing * samples).toLong
val zero: Long = testObject.nextWait()
  zero should be (0l)
println("..., wait ", next)
println("... , wait " + next)

Context

StackExchange Code Review Q#128241, answer score: 6

Revisions (0)

No revisions yet.