lib/simple_throttle.rb in simple_throttle-1.0.3 vs lib/simple_throttle.rb in simple_throttle-1.0.4
- old
+ new
@@ -1,8 +1,9 @@
# frozen_string_literal: true
require "redis"
+
# Create a simple throttle that can be used to limit the number of request for a resouce
# per time period. These objects are thread safe.
class SimpleThrottle
# Server side Lua script that maintains the throttle in redis. The throttle is stored as a list
# of timestamps in milliseconds. When the script is invoked it will scan the oldest entries
@@ -39,18 +40,29 @@
@lock = Mutex.new
class << self
# Add a global throttle that can be referenced later with the [] method.
- def add(name, limit:, ttl:, redis: nil)
+ # This can be used to configure global throttles that you want to setup once
+ # and then use in multiple places.
+ #
+ # @param name [String] unique name for the throttle
+ # @param ttl [Numeric] number of seconds that the throttle will remain active
+ # @param limit [Integer] number of allowed requests within the throttle ttl
+ # @param redis [Redis, Proc] Redis instance to use or a Proc that yields a Redos instance
+ # @return [void]
+ def add(name, ttl:, limit:, redis: nil)
@lock.synchronize do
@throttles ||= {}
@throttles[name.to_s] = new(name, limit: limit, ttl: ttl, redis: redis)
end
end
# Returns a globally defined throttle with the specfied name.
+ #
+ # @param name [String, Symbol] name of the throttle
+ # @return [SimpleThrottle]
def [](name)
if defined?(@throttles) && @throttles
@throttles[name.to_s]
end
end
@@ -58,15 +70,21 @@
# Set the Redis instance to use for maintaining the throttle. This can either be set
# with a hard coded value or by the value yielded by a block. If the block form is used
# it will be invoked at runtime to get the instance. Use this method if your Redis instance
# isn't constant (for example if you're in a forking environment and re-initialize connections
# on fork)
+ #
+ # @param client [Redis, Proc]
+ # @yieldreturn [Redis]
+ # @return [void]
def set_redis(client = nil, &block)
@redis_client = (client || block)
end
# Return the Redis instance where the throttles are stored.
+ #
+ # @return [Redis]
def redis
@redis_client ||= Redis.new
if @redis_client.is_a?(Proc)
@redis_client.call
else
@@ -91,50 +109,59 @@
end
end
attr_reader :name, :limit, :ttl
- # Create a new throttle
+ # Create a new throttle.
+ #
# @param name [String] unique name for the throttle
# @param ttl [Numeric] number of seconds that the throttle will remain active
# @param limit [Integer] number of allowed requests within the throttle ttl
- # @param redis [Redis] Redis client to use
+ # @param redis [Redis, Proc] Redis instance to use or a Proc that yields a Redos instance
def initialize(name, ttl:, limit:, redis: nil)
@name = name.to_s
@name = name.dup.freeze unless name.frozen?
@limit = limit.to_i
@ttl = ttl.to_f
@redis = redis
end
# Returns true if the limit for the throttle has not been reached yet. This method
# will also track the throttled resource as having been invoked on each call.
+ #
+ # @return [Boolean]
def allowed!
size = current_size(true)
size < limit
end
# Reset a throttle back to zero.
+ #
+ # @return [void]
def reset!
redis_client.del(redis_key)
end
# Peek at the current number for throttled calls being tracked.
+ #
+ # @return [Integer]
def peek
current_size(false)
end
# Returns when the next resource call should be allowed. Note that this doesn't guarantee that
# calling allow! will return true if the wait time is zero since other processes or threads can
# claim the resource.
+ #
+ # @return [Float]
def wait_time
if peek < limit
0.0
else
first = redis_client.lindex(redis_key, 0).to_f / 1000.0
delta = Time.now.to_f - first
delta = 0.0 if delta < 0
- delta
+ ttl - delta
end
end
private