class Zhong::Scheduler

Constants

DEFAULT_CONFIG
TRAPPED_SIGNALS

Attributes

jobs[R]

Public Class Methods

new(config = {}) click to toggle source
# File lib/zhong/scheduler.rb, line 14
def initialize(config = {})
  @jobs = {}
  @callbacks = {}
  @config = DEFAULT_CONFIG.merge(config)

  @category = nil
  @error_handler = nil
  @running = false
end

Public Instance Methods

category(name) { |self| ... } click to toggle source
# File lib/zhong/scheduler.rb, line 32
def category(name)
  raise "cannot nest categories: #{name} would be nested in #{@category} (#{caller.first})" if @category

  @category = name.to_s

  yield(self)

  @category = nil
end
clear() click to toggle source
# File lib/zhong/scheduler.rb, line 24
def clear
  raise "unable to clear while running; run Zhong.stop first" if @running

  @jobs = {}
  @callbacks = {}
  @category = nil
end
error_handler(&block) click to toggle source
# File lib/zhong/scheduler.rb, line 52
def error_handler(&block)
  @error_handler = block if block_given?
  @error_handler
end
every(period, name, opts = {}, &block) click to toggle source
# File lib/zhong/scheduler.rb, line 42
def every(period, name, opts = {}, &block)
  raise "must specify a period for #{name} (#{caller.first})" unless period

  job = Job.new(name, opts.merge(@config).merge(every: period, category: @category), @callbacks, &block)

  raise "duplicate job #{job}" if jobs.key?(job.id)

  @jobs[job.id] = job
end
find_by_name(job_name) click to toggle source
# File lib/zhong/scheduler.rb, line 111
def find_by_name(job_name)
  @jobs[Digest::SHA256.hexdigest(job_name)]
end
on(event, &block) click to toggle source
# File lib/zhong/scheduler.rb, line 57
def on(event, &block)
  unless [:before_tick, :after_tick, :before_run, :after_run, :before_disable,
          :after_disable, :before_enable, :after_enable].include?(event.to_sym)
    raise "unknown callback #{event}"
  end
  (@callbacks[event.to_sym] ||= []) << block
end
redis_time() click to toggle source
# File lib/zhong/scheduler.rb, line 115
def redis_time
  s, ms = redis.time # returns [seconds since epoch, microseconds]
  now = Time.at(s + ms / (10**6))
  tz ? now.in_time_zone(tz) : now
end
start() click to toggle source
# File lib/zhong/scheduler.rb, line 65
def start
  logger.info "starting at #{redis_time}"

  @stop = false

  trap_signals

  raise "already running" if @running

  loop do
    @running = true

    if fire_callbacks(:before_tick)
      now = redis_time

      jobs_to_run(now).each do |_, job|
        break if @stop
        run_job(job, now)
      end

      break if @stop

      fire_callbacks(:after_tick)

      heartbeat(now)

      break if @stop
    else
      logger.info "skipping tick due to a `:before_tick` callback"
    end

    sleep_until_next_second

    break if @stop
  end

  @running = false

  Thread.new { logger.info "stopped" }.join
end
stop() click to toggle source
# File lib/zhong/scheduler.rb, line 106
def stop
  Thread.new { logger.error "stopping" } if @running # thread necessary due to trap context
  @stop = true
end

Private Instance Methods

fire_callbacks(event, *args) click to toggle source
# File lib/zhong/scheduler.rb, line 126
def fire_callbacks(event, *args)
  @callbacks[event].to_a.map do |callback|
    callback.call(*args)
  end.compact.all? # do not skip on nils
end
heartbeat(time) click to toggle source
# File lib/zhong/scheduler.rb, line 147
def heartbeat(time)
  redis.hset(heartbeat_key, heartbeat_field, time.to_i)
end
heartbeat_field() click to toggle source
# File lib/zhong/scheduler.rb, line 151
def heartbeat_field
  @heartbeat_field ||= "#{`hostname`.strip}##{Process.pid}"
end
jobs_to_run(time = redis_time) click to toggle source
# File lib/zhong/scheduler.rb, line 132
def jobs_to_run(time = redis_time)
  jobs.select { |_, job| job.run?(time) }
end
run_job(job, time = redis_time) click to toggle source
# File lib/zhong/scheduler.rb, line 136
def run_job(job, time = redis_time)
  unless fire_callbacks(:before_run, job, time)
    logger.info "skipping #{job} due to a `:before_run` callback"
    return
  end

  ran = job.run(time, error_handler)

  fire_callbacks(:after_run, job, time, ran)
end
sleep_until_next_second() click to toggle source
# File lib/zhong/scheduler.rb, line 161
def sleep_until_next_second
  GC.start
  sleep(1.0 - Time.now.subsec + 0.0001)
end
trap_signals() click to toggle source
# File lib/zhong/scheduler.rb, line 155
def trap_signals
  TRAPPED_SIGNALS.each do |sig|
    Signal.trap(sig) { stop }
  end
end