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