module Eventbox::Timer

Simple timer services for Eventboxes

This module can be included into Eventbox classes to add simple timer functions.

class MyBox < Eventbox
  include Eventbox::Timer

  async_call def init
    super     # Make sure Timer#init is called
    timer_after(1) do
      puts "one second elapsed"
    end
  end
end

MyBox.new     # Schedule the alarm after 1 sec
sleep 2       # Wait for the timer to be triggered

The main functions are timer_after and timer_every. They schedule asynchronous calls to the given block:

timer_after(3.0) do
  # executed once after 3 seconds
end

timer_every(1.5) do
  # executed repeatedly every 1.5 seconds
end

Both functions return an {Alarm} object which can be used to cancel the alarm through {timer_cancel}.

{Timer} always uses one {Eventbox::Boxable#action action} thread per Eventbox object, regardless of the number of scheduled timers. All alarms are called from this thread. {timer_after}, {timer_every} and {timer_cancel} can be used within the {file:README.md#event-scope event scope}, in actions and from external scope. Alarms defined within the event scope must be non-blocking, as any other code in the event scope. Alarms defined in action or external scope should also avoid blocking code, otherwise one alarm can delay the next alarm.

Note: Each object that includes the {Timer} module must be explicit terminated by {Eventbox#shutdown!}. It is (currently) not freed by the garbarge collector.

Public Instance Methods

init(*args) click to toggle source

@private

Calls superclass method
# File lib/eventbox/timer.rb, line 85
                   def init(*args)
  super
  @timer_alarms = []
  @timer_action = timer_start_worker
end
timer_after(seconds, now=Time.now, &block) click to toggle source
# File lib/eventbox/timer.rb, line 116
          def timer_after(seconds, now=Time.now, &block)
  a = OneTimeAlarm.new(now + seconds, &block)
  timer_add_alarm(a)
  a
end
timer_cancel(alarm) click to toggle source
# File lib/eventbox/timer.rb, line 137
           def timer_cancel(alarm)
  a = @timer_alarms.delete(alarm)
  if a
    timer_check_integrity
  end
end
timer_every(seconds, now=Time.now, &block) click to toggle source
# File lib/eventbox/timer.rb, line 128
          def timer_every(seconds, now=Time.now, &block)
  a = RepeatedAlarm.new(now + seconds, seconds, &block)
  timer_add_alarm(a)
  a
end
timer_fire(now=Time.now) click to toggle source

@private

# File lib/eventbox/timer.rb, line 170
                  def timer_fire(now=Time.now)
  while @timer_alarms.last&.timestamp&.<=(now)
    a = @timer_alarms.pop
    if a.fire_then_repeat?(now)
      timer_add_alarm(a)
    end
    timer_check_integrity
  end
  # the method result is irrelevant, but sync_call is necessary to yield the timer blocks
  nil
end
timer_next_timestamp() click to toggle source

@private

# File lib/eventbox/timer.rb, line 165
                  def timer_next_timestamp
  @timer_alarms.last&.timestamp
end
timer_start_worker() click to toggle source

@private

# File lib/eventbox/timer.rb, line 92
               def timer_start_worker
  loop do
    begin
      interval = timer_next_timestamp&.-(Time.now)
      Thread.handle_interrupt(Reload => :on_blocking) do
        if interval.nil?
          Kernel.sleep
        elsif interval > 0.0
          Kernel.sleep(interval)
        end
      end
    rescue Reload
    else
      timer_fire
    end
  end
end

Private Instance Methods

timer_add_alarm(alarm) click to toggle source

@private

# File lib/eventbox/timer.rb, line 145
        def timer_add_alarm(alarm)
  i = @timer_alarms.bsearch_index {|t| t.timestamp <= alarm.timestamp }
  if i
    @timer_alarms[i, 0] = alarm
  else
    @timer_alarms << alarm
    @timer_action.raise(Reload) unless @timer_action.current?
  end
  timer_check_integrity
end
timer_check_integrity() click to toggle source

@private

# File lib/eventbox/timer.rb, line 157
        def timer_check_integrity
  @timer_alarms.inject(nil) do |min, a|
    raise InternalError, "alarms are not ordered: #{@timer_alarms.inspect}" if min && min < a.timestamp
    a.timestamp
  end
end