All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.devslash.RateLimiter.kt Maven / Gradle / Ivy

package net.devslash

import kotlinx.coroutines.delay
import kotlinx.coroutines.sync.Mutex
import java.time.Clock
import java.time.Duration
import java.time.Instant
import kotlin.math.max

/**
 * This rate limiter isn't perfect but aims to ensure the rate isn't going too fast, and that the rate is smooth. This
 * means that calls to acquire will only unblock one at a time. If two concurrent calls are made to acquire, then the
 * first will unblock straight away. The second one will unblock after `1 / QPS` seconds.
 *
 * This means that the rate limiter effectively has no memory of the past, beyond the call before. This means that if
 * there is inconsistency with how fast requests are performed, then the rate limit may be over-restrictive.
 */
class AcquiringRateLimiter(
  private val rateLimitOptions: RateLimitOptions,
  clock: Clock = Clock.systemUTC()
) {

  // This equals how many milliseconds it takes to release a ticket
  private val stepMillis = rateLimitOptions.duration.toMillis() / max(1, rateLimitOptions.count)
  private val ticket = TimedTicket(stepMillis, clock)
  private val lock = Mutex()

  suspend fun acquire() {
    if (!rateLimitOptions.enabled) {
      return
    }

    lock.lock()
    val delayMs = ticket.timeTillNextRelease()
    delay(delayMs)
    ticket.setLastRelease()
    lock.unlock()
  }
}

/**
 * This class is split out from the Rate limiter to allow for better testing
 * It effectively should manage the timesteps in which a request ticket can be
 * allowed
 */
class TimedTicket(private val millSteps: Long, private val clock: Clock) {
  private var lastRelease = Instant.ofEpochMilli(0)

  fun timeTillNextRelease(): Long {
    val now = clock.instant()
    val timeSinceLastRelease = Duration.between(lastRelease, now).toMillis()
    return (millSteps - timeSinceLastRelease).coerceAtLeast(0)
  }

  fun setLastRelease() { lastRelease = clock.instant() }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy