class Berater::Limiter

Attributes

args[R]
capacity[R]
key[R]
options[R]

Public Class Methods

new(key, capacity, *args, **opts) click to toggle source
# File lib/berater/limiter.rb, line 71
def initialize(key, capacity, *args, **opts)
  @key = key
  self.capacity = capacity
  @args = args
  @options = opts
end

Protected Class Methods

cache_key(key) click to toggle source
# File lib/berater/limiter.rb, line 109
def cache_key(key)
  klass = to_s.split(':')[-1]
  "Berater:#{klass}:#{key}"
end
inherited(subclass) click to toggle source
# File lib/berater/limiter.rb, line 116
def inherited(subclass)
  # automagically create convenience method
  name = subclass.to_s.split(':')[-1]
  Berater.define_singleton_method(name) do |*args, **opts, &block|
    Berater::Utils.convenience_fn(subclass, *args, **opts, &block)
  end
end
new(*) click to toggle source
Calls superclass method
# File lib/berater/limiter.rb, line 102
def new(*)
  # can only call via subclass
  raise NoMethodError if self == Berater::Limiter

  super
end

Public Instance Methods

==(other) click to toggle source
# File lib/berater/limiter.rb, line 58
def ==(other)
  self.class == other.class &&
  self.key == other.key &&
  self.capacity == other.capacity &&
  self.args == other.args &&
  self.options == other.options &&
  self.redis.connection == other.redis.connection
end
limit(capacity: nil, cost: 1) { |lock| ... } click to toggle source
# File lib/berater/limiter.rb, line 10
def limit(capacity: nil, cost: 1, &block)
  capacity ||= @capacity
  lock = nil

  Berater.middleware.call(self, capacity: capacity, cost: cost) do |limiter, **opts|
    lock = limiter.inner_limit(**opts)
  end

  if block_given?
    begin
      yield lock
    ensure
      lock.release
    end
  else
    lock
  end
end
redis() click to toggle source
# File lib/berater/limiter.rb, line 6
def redis
  options[:redis] || Berater.redis
end
utilization() click to toggle source
# File lib/berater/limiter.rb, line 46
def utilization
  lock = limit(cost: 0)

  if lock.capacity == 0
    1.0
  else
    lock.contention.to_f / lock.capacity
  end
rescue Berater::Overloaded
  1.0
end

Protected Instance Methods

acquire_lock(capacity, cost) click to toggle source
# File lib/berater/limiter.rb, line 92
def acquire_lock(capacity, cost)
  raise NotImplementedError
end
cache_key(subkey = nil) click to toggle source
# File lib/berater/limiter.rb, line 96
def cache_key(subkey = nil)
  instance_key = subkey.nil? ? key : "#{key}:#{subkey}"
  self.class.cache_key(instance_key)
end
capacity=(capacity) click to toggle source
# File lib/berater/limiter.rb, line 78
def capacity=(capacity)
  unless capacity.is_a?(Numeric)
    raise ArgumentError, "expected Numeric, found #{capacity.class}"
  end

  if capacity == Float::INFINITY
    raise ArgumentError, 'infinite capacity not supported, use Unlimiter'
  end

  raise ArgumentError, 'capacity must be >= 0' unless capacity >= 0

  @capacity = capacity
end
inner_limit(capacity:, cost:) click to toggle source
# File lib/berater/limiter.rb, line 29
          def inner_limit(capacity:, cost:)
  unless capacity.is_a?(Numeric) && capacity >= 0
    raise ArgumentError, "invalid capacity: #{capacity}"
  end

  unless cost.is_a?(Numeric) && cost >= 0 && cost < Float::INFINITY
    raise ArgumentError, "invalid cost: #{cost}"
  end

  acquire_lock(capacity, cost)
rescue NoMethodError => e
  raise unless e.message.include?("undefined method `evalsha' for")

  # repackage error so it's easier to understand
  raise RuntimeError, "invalid redis connection: #{redis}"
end