Skip to content

Instantly share code, notes, and snippets.

@IronSavior
Last active January 8, 2016 18:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IronSavior/3c3615193c0884b7ad3b to your computer and use it in GitHub Desktop.
Save IronSavior/3c3615193c0884b7ad3b to your computer and use it in GitHub Desktop.
Simple Rate Limiter
# Enforces upper bound on the frequency of arbitrary actions.
# Thread-safe, but not terribly efficient.
class RateLimit
# Blocks passed to #enforce are executed with a frequency not exceeding the average of +limit+ calls per +period+ over
# +burst+ consecutive periods. Thread safe, but be advised there are no guarantees about the order or timeliness in
# which the given blocks are executed.
# @param limit [Numeric] Target average number of events per period
# @param period [Numeric] Duration of period in seconds (or equivalent, like ActiveSupport::Duration)
# @param burst [Numeric] Number of periods over which to average
def initialize( limit, period = 1, burst = 3 )
fail ArgumentError, 'limit must be greater than zero (given %s)' % limit unless limit > 0
fail ArgumentError, 'period must be greater than zero (given %s)' % period unless period > 0
fail ArgumentError, 'burst must be greater than zero (given %s)' % burst unless burst > 0
@limit = burst * limit
@period = burst * period
@max_sleep = period.to_f / limit
@events = Array[]
@mutex = Mutex.new
freeze
end
# Execute the given block when rate constraints allow
def enforce
when_ready{ record_event }
yield
end
private
def when_ready
@mutex.synchronize do
sleep until ready?
yield
end
end
def ready?
expire_events
@events.size < @limit
end
def record_event
@events << Time.now
end
def sleep
@mutex.sleep Random.rand(@max_sleep)
end
def expire_events
expiry = Time.now - @period
@events.reject!{ |t| t < expiry || break }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment