Last active
January 8, 2016 18:38
-
-
Save IronSavior/3c3615193c0884b7ad3b to your computer and use it in GitHub Desktop.
Simple Rate Limiter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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