Skip to content

Instantly share code, notes, and snippets.

@betawaffle
Last active December 28, 2015 22:49
Show Gist options
  • Save betawaffle/7573907 to your computer and use it in GitHub Desktop.
Save betawaffle/7573907 to your computer and use it in GitHub Desktop.
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