2012-01-22 19:01:46 -05:00
|
|
|
require 'celluloid'
|
|
|
|
require 'redis'
|
|
|
|
require 'multi_json'
|
|
|
|
|
2012-01-23 17:05:03 -05:00
|
|
|
require 'sidekiq/util'
|
2012-01-25 16:32:51 -05:00
|
|
|
require 'sidekiq/processor'
|
2012-01-22 19:01:46 -05:00
|
|
|
|
2012-01-16 19:14:47 -05:00
|
|
|
module Sidekiq
|
|
|
|
|
|
|
|
##
|
2012-02-03 13:02:57 -05:00
|
|
|
# The main router in the system. This
|
2012-01-25 16:32:51 -05:00
|
|
|
# manages the processor state and fetches messages
|
2012-02-03 13:02:57 -05:00
|
|
|
# from Redis to be dispatched to an idle processor.
|
2012-01-16 19:14:47 -05:00
|
|
|
#
|
2012-02-03 13:02:57 -05:00
|
|
|
class Manager
|
2012-01-22 14:32:38 -05:00
|
|
|
include Util
|
2012-01-16 23:02:58 -05:00
|
|
|
include Celluloid
|
2012-01-16 19:18:36 -05:00
|
|
|
|
2012-01-25 16:32:51 -05:00
|
|
|
trap_exit :processor_died
|
2012-01-22 14:32:38 -05:00
|
|
|
|
2012-02-08 17:43:57 -05:00
|
|
|
def initialize(redis, options={})
|
|
|
|
log "Booting sidekiq #{Sidekiq::VERSION} with Redis at #{redis.client.location}"
|
2012-01-22 19:01:46 -05:00
|
|
|
verbose options.inspect
|
2012-02-03 13:02:57 -05:00
|
|
|
@count = options[:processor_count] || 25
|
2012-01-22 14:32:38 -05:00
|
|
|
@queues = options[:queues]
|
|
|
|
@queue_idx = 0
|
|
|
|
@queues_size = @queues.size
|
2012-02-08 17:43:57 -05:00
|
|
|
@redis = redis
|
2012-02-05 16:22:57 -05:00
|
|
|
@done_callback = nil
|
2012-01-22 14:32:38 -05:00
|
|
|
|
2012-01-22 19:01:46 -05:00
|
|
|
@done = false
|
|
|
|
@busy = []
|
|
|
|
@ready = []
|
|
|
|
@count.times do
|
2012-01-25 16:32:51 -05:00
|
|
|
@ready << Processor.new_link(current_actor)
|
2012-01-22 19:01:46 -05:00
|
|
|
end
|
2012-01-22 14:32:38 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def stop
|
|
|
|
@done = true
|
|
|
|
@ready.each(&:terminate)
|
|
|
|
@ready.clear
|
|
|
|
|
2012-01-23 17:05:03 -05:00
|
|
|
after(5) do
|
|
|
|
signal(:shutdown)
|
2012-01-22 14:32:38 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def start
|
2012-01-24 01:08:38 -05:00
|
|
|
dispatch(true)
|
2012-01-22 14:32:38 -05:00
|
|
|
end
|
2012-01-16 19:18:36 -05:00
|
|
|
|
2012-02-03 13:02:57 -05:00
|
|
|
def when_done
|
|
|
|
@done_callback = Proc.new
|
|
|
|
end
|
|
|
|
|
2012-01-25 16:32:51 -05:00
|
|
|
def processor_done(processor)
|
2012-02-05 16:22:57 -05:00
|
|
|
@done_callback.call(processor) if @done_callback
|
2012-01-25 16:32:51 -05:00
|
|
|
@busy.delete(processor)
|
2012-01-22 14:32:38 -05:00
|
|
|
if stopped?
|
2012-01-25 16:32:51 -05:00
|
|
|
processor.terminate
|
2012-01-22 14:32:38 -05:00
|
|
|
else
|
2012-01-25 16:32:51 -05:00
|
|
|
@ready << processor
|
2012-01-16 23:02:58 -05:00
|
|
|
end
|
2012-01-22 14:32:38 -05:00
|
|
|
dispatch
|
2012-01-16 19:18:36 -05:00
|
|
|
end
|
2012-01-16 23:02:58 -05:00
|
|
|
|
2012-01-25 16:32:51 -05:00
|
|
|
def processor_died(processor, reason)
|
|
|
|
@busy.delete(processor)
|
2012-01-23 17:05:03 -05:00
|
|
|
|
|
|
|
if reason
|
2012-01-25 16:32:51 -05:00
|
|
|
log "Processor death: #{reason}"
|
2012-01-23 17:05:03 -05:00
|
|
|
log reason.backtrace.join("\n")
|
|
|
|
end
|
2012-01-22 19:01:46 -05:00
|
|
|
|
|
|
|
unless stopped?
|
2012-01-25 16:32:51 -05:00
|
|
|
@ready << Processor.new_link(current_actor)
|
2012-01-22 19:01:46 -05:00
|
|
|
dispatch
|
|
|
|
end
|
2012-01-22 14:32:38 -05:00
|
|
|
end
|
|
|
|
|
2012-01-22 19:01:46 -05:00
|
|
|
private
|
|
|
|
|
2012-01-24 01:08:38 -05:00
|
|
|
def find_work(queue_idx)
|
|
|
|
current_queue = @queues[queue_idx]
|
2012-02-07 20:18:28 -05:00
|
|
|
msg = @redis.lpop("queue:#{current_queue}")
|
2012-01-24 01:08:38 -05:00
|
|
|
if msg
|
2012-01-25 16:32:51 -05:00
|
|
|
processor = @ready.pop
|
|
|
|
@busy << processor
|
2012-02-08 20:04:02 -05:00
|
|
|
processor.process!(MultiJson.decode(msg))
|
2012-01-24 01:08:38 -05:00
|
|
|
end
|
2012-02-03 13:02:57 -05:00
|
|
|
!!msg
|
2012-01-24 01:08:38 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def dispatch(schedule = false)
|
2012-01-22 14:32:38 -05:00
|
|
|
watchdog("Fatal error in sidekiq, dispatch loop died") do
|
|
|
|
return if stopped?
|
|
|
|
|
|
|
|
# Our dispatch loop
|
2012-01-24 01:08:38 -05:00
|
|
|
# Loop through the queues, looking for a message in each.
|
2012-01-22 14:32:38 -05:00
|
|
|
queue_idx = 0
|
2012-01-24 01:08:38 -05:00
|
|
|
found = false
|
2012-01-22 14:32:38 -05:00
|
|
|
loop do
|
2012-01-25 16:32:51 -05:00
|
|
|
# return so that we don't dispatch again until processor_done
|
|
|
|
break verbose('no processors') if @ready.size == 0
|
2012-01-22 14:32:38 -05:00
|
|
|
|
2012-01-24 01:08:38 -05:00
|
|
|
found ||= find_work(queue_idx)
|
2012-01-22 14:32:38 -05:00
|
|
|
queue_idx += 1
|
|
|
|
|
|
|
|
# if we find no messages in any of the queues, we can break
|
|
|
|
# out of the loop. Otherwise we loop again.
|
2012-01-22 19:01:46 -05:00
|
|
|
lastq = (queue_idx % @queues.size == 0)
|
2012-01-24 01:08:38 -05:00
|
|
|
if lastq && !found
|
|
|
|
verbose('nothing to process'); break
|
2012-01-22 19:01:46 -05:00
|
|
|
elsif lastq
|
2012-01-22 14:32:38 -05:00
|
|
|
queue_idx = 0
|
2012-01-24 01:08:38 -05:00
|
|
|
found = false
|
2012-01-22 14:32:38 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-02-03 13:02:57 -05:00
|
|
|
# This is the polling loop that ensures we check Redis every
|
|
|
|
# second for work, even if there was nothing to do this time
|
|
|
|
# around.
|
2012-01-24 01:08:38 -05:00
|
|
|
after(1) { verbose('ping'); dispatch(schedule) } if schedule
|
2012-01-22 14:32:38 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def stopped?
|
|
|
|
@done
|
|
|
|
end
|
2012-01-16 19:14:47 -05:00
|
|
|
end
|
|
|
|
end
|