Last active
December 28, 2015 22:49
-
-
Save betawaffle/7573907 to your computer and use it in GitHub Desktop.
This file contains 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
class Timers | |
def initialize | |
@timers = SortedSet.new | |
@mutex = Mutex.new | |
@mutex.synchronize do | |
@thread = Thread.new { loop } | |
@thread.abort_on_exception = true | |
end | |
end | |
def add(timer) | |
return unless timer.time | |
synchronize do | |
@timers.add(timer) | |
@thread.wakeup | |
end | |
timer | |
end | |
def after(seconds, proc = Proc.new) | |
schedule(Time.now + seconds, proc) | |
end | |
def backoff(range, stepper, proc = Proc.new) | |
complex BackoffIntervals.new(range, stepper), proc | |
end | |
def complex(intervals, proc = Proc.new) | |
add ComplexTimer.new(intervals, proc) | |
end | |
def exponential_backoff(range, base = 2, proc = Proc.new) | |
backoff(range, ->(x) { x * base }, proc) | |
end | |
def linear_backoff(range, step = 1, proc = Proc.new) | |
backoff(range, ->(x) { x + step }, proc) | |
end | |
def schedule(time, proc = Proc.new) | |
add Timer.new(time, proc) | |
end | |
private | |
# Synchronized by `loop'. Do not run this yourself! | |
def fire(now = nil) | |
while true | |
timer = @timers.first or return | |
now ||= Time.now + 0.001 | |
now >= timer.time or return timer.time | |
@timers.delete(timer) | |
unsynchronize { timer.(self) } | |
end | |
end | |
def loop | |
synchronize do | |
if time = fire | |
sleep(time - Time.now) | |
else | |
sleep | |
end | |
end while true | |
end | |
def sleep(timeout = nil) | |
@mutex.sleep(timeout) | |
end | |
def synchronize | |
@mutex.synchronize { yield } | |
end | |
def unsynchronize | |
@mutex.unlock | |
begin | |
yield | |
ensure | |
@mutex.lock | |
end | |
end | |
end # Timers | |
class Timer | |
include Comparable | |
attr_reader :time | |
def initialize(time, proc = Proc.new) | |
@proc = proc | |
@time = time | |
end | |
def <=>(other) | |
@time <=> other.time | |
end | |
def call(timers) | |
@proc.() | |
end | |
def inspect | |
"#<#{self.class} #{@time.inspect}>" | |
end | |
end | |
class ComplexTimer < Timer | |
def initialize(intervals, proc = Proc.new) | |
@intervals = intervals.respond_to?(:next) ? intervals : intervals.to_enum | |
@cancelled = nil | |
@last = Time.now | |
super next_time, proc | |
end | |
def call(timers) | |
return if @cancelled | |
@last = Time.now | |
@proc.(self) | |
@time = next_time | |
timers.add self | |
end | |
def cancel | |
@cancelled = true | |
self | |
end | |
def reset | |
@intervals.rewind | |
self | |
end | |
private | |
def next_time | |
return if @cancelled | |
begin | |
interval = @intervals.next | |
rescue StopIteration | |
@cancelled = true | |
nil | |
else | |
@last + interval | |
end | |
end | |
end # ComplexTimer | |
class BackoffIntervals | |
def initialize(range, proc = Proc.new) | |
@range = range | |
@value = range.min | |
@proc = proc | |
end | |
def rewind | |
@value = range.min | |
end | |
def next | |
@value = @proc.(value = @value) | |
@value = @range.max if @value > @range.max | |
value | |
end | |
end # BackoffIntervals |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment