woodhouse 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/Guardfile +4 -0
- data/MIT-LICENSE +21 -0
- data/PROGRESS-NOTES.txt +5 -0
- data/README.markdown +152 -0
- data/Rakefile +35 -0
- data/THOUGHTS +84 -0
- data/doc/example/script-woodhouse +9 -0
- data/doc/example/woodhouse-initializer.rb +12 -0
- data/lib/generators/woodhouse_generator.rb +38 -0
- data/lib/woodhouse/dispatcher.rb +37 -0
- data/lib/woodhouse/dispatchers/bunny_dispatcher.rb +48 -0
- data/lib/woodhouse/dispatchers/common_amqp_dispatcher.rb +28 -0
- data/lib/woodhouse/dispatchers/hot_bunnies_dispatcher.rb +48 -0
- data/lib/woodhouse/dispatchers/local_dispatcher.rb +13 -0
- data/lib/woodhouse/dispatchers/local_pool_dispatcher.rb +25 -0
- data/lib/woodhouse/dispatchers.rb +19 -0
- data/lib/woodhouse/extension.rb +24 -0
- data/lib/woodhouse/extensions/new_relic/instrumentation_middleware.rb +10 -0
- data/lib/woodhouse/extensions/new_relic.rb +23 -0
- data/lib/woodhouse/extensions/progress.rb +165 -0
- data/lib/woodhouse/job.rb +76 -0
- data/lib/woodhouse/job_execution.rb +60 -0
- data/lib/woodhouse/layout.rb +290 -0
- data/lib/woodhouse/layout_builder.rb +55 -0
- data/lib/woodhouse/layout_serializer.rb +82 -0
- data/lib/woodhouse/middleware/airbrake_exceptions.rb +12 -0
- data/lib/woodhouse/middleware/assign_logger.rb +10 -0
- data/lib/woodhouse/middleware/log_dispatch.rb +21 -0
- data/lib/woodhouse/middleware/log_jobs.rb +22 -0
- data/lib/woodhouse/middleware.rb +16 -0
- data/lib/woodhouse/middleware_stack.rb +35 -0
- data/lib/woodhouse/mixin_registry.rb +27 -0
- data/lib/woodhouse/node_configuration.rb +80 -0
- data/lib/woodhouse/process.rb +41 -0
- data/lib/woodhouse/queue_criteria.rb +52 -0
- data/lib/woodhouse/rails.rb +46 -0
- data/lib/woodhouse/rails2.rb +21 -0
- data/lib/woodhouse/registry.rb +12 -0
- data/lib/woodhouse/runner.rb +60 -0
- data/lib/woodhouse/runners/bunny_runner.rb +60 -0
- data/lib/woodhouse/runners/dummy_runner.rb +11 -0
- data/lib/woodhouse/runners/hot_bunnies_runner.rb +79 -0
- data/lib/woodhouse/runners.rb +16 -0
- data/lib/woodhouse/scheduler.rb +113 -0
- data/lib/woodhouse/server.rb +80 -0
- data/lib/woodhouse/trigger_set.rb +19 -0
- data/lib/woodhouse/version.rb +3 -0
- data/lib/woodhouse/worker.rb +86 -0
- data/lib/woodhouse.rb +99 -0
- data/spec/integration/bunny_worker_process_spec.rb +32 -0
- data/spec/layout_builder_spec.rb +55 -0
- data/spec/layout_spec.rb +143 -0
- data/spec/middleware_stack_spec.rb +56 -0
- data/spec/mixin_registry_spec.rb +15 -0
- data/spec/node_configuration_spec.rb +22 -0
- data/spec/progress_spec.rb +40 -0
- data/spec/queue_criteria_spec.rb +11 -0
- data/spec/scheduler_spec.rb +41 -0
- data/spec/server_spec.rb +72 -0
- data/spec/shared_contexts.rb +70 -0
- data/spec/worker_spec.rb +28 -0
- data/woodhouse.gemspec +37 -0
- metadata +285 -0
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Woodhouse::LayoutSerializer
|
4
|
+
|
5
|
+
def initialize(layout)
|
6
|
+
@layout = layout
|
7
|
+
end
|
8
|
+
|
9
|
+
def as_hash
|
10
|
+
{
|
11
|
+
:nodes => node_list(layout.nodes)
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_json
|
16
|
+
as_hash.to_json
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.dump(layout)
|
20
|
+
new(layout).to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load(json)
|
24
|
+
LayoutLoader.new(json).layout
|
25
|
+
end
|
26
|
+
|
27
|
+
class LayoutLoader
|
28
|
+
|
29
|
+
def initialize(json)
|
30
|
+
@entries = JSON.parse(json)
|
31
|
+
end
|
32
|
+
|
33
|
+
def layout
|
34
|
+
Woodhouse::Layout.new.tap do |layout|
|
35
|
+
@entries['nodes'].each do |node|
|
36
|
+
new_node = layout.add_node(node['name'])
|
37
|
+
node['workers'].each do |worker|
|
38
|
+
new_node.add_worker Woodhouse::Layout::Worker.new(worker['worker_class_name'], worker['job_method'], :threads => worker['threads'], :only => worker['criteria'])
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_reader :layout
|
49
|
+
|
50
|
+
def node_list(nodes)
|
51
|
+
nodes.map{|node|
|
52
|
+
node_hash(node)
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def node_hash(node)
|
57
|
+
{
|
58
|
+
:name => node.name,
|
59
|
+
:workers => worker_list(node.workers)
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def worker_list(workers)
|
64
|
+
workers.map{|worker|
|
65
|
+
worker_hash(worker)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def worker_hash(worker)
|
70
|
+
{
|
71
|
+
:worker_class_name => worker.worker_class_name,
|
72
|
+
:job_method => worker.job_method,
|
73
|
+
:threads => worker.threads,
|
74
|
+
:criteria => criteria_hash(worker.criteria)
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def criteria_hash(criteria)
|
79
|
+
criteria.criteria
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Woodhouse::Middleware::LogDispatch < Woodhouse::Middleware
|
2
|
+
|
3
|
+
def call(job)
|
4
|
+
begin
|
5
|
+
yield job
|
6
|
+
rescue => err
|
7
|
+
log "#{job.describe} could not be dispatched: #{err.inspect}"
|
8
|
+
raise err
|
9
|
+
end
|
10
|
+
log "#{job.describe} dispatched"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def log(msg)
|
16
|
+
if @config.logger
|
17
|
+
@config.logger.info msg
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Woodhouse::Middleware::LogJobs < Woodhouse::Middleware
|
2
|
+
|
3
|
+
def call(job, worker)
|
4
|
+
log "#{job.describe} starting"
|
5
|
+
begin
|
6
|
+
yield job, worker
|
7
|
+
rescue => err
|
8
|
+
log "#{job.describe} failed: #{err.inspect}"
|
9
|
+
raise err
|
10
|
+
end
|
11
|
+
log "#{job.describe} done"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def log(msg)
|
17
|
+
if @config.logger
|
18
|
+
@config.logger.info msg
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Woodhouse::Middleware
|
2
|
+
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(*args)
|
8
|
+
yield *args
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'woodhouse/middleware/log_jobs'
|
14
|
+
require 'woodhouse/middleware/log_dispatch'
|
15
|
+
require 'woodhouse/middleware/assign_logger'
|
16
|
+
require 'woodhouse/middleware/airbrake_exceptions'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Woodhouse::MiddlewareStack < Array
|
2
|
+
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(*args, &final)
|
8
|
+
stack = make_stack.dup
|
9
|
+
next_step = lambda {|*args|
|
10
|
+
next_item = stack.shift
|
11
|
+
if next_item.nil?
|
12
|
+
final.call(*args)
|
13
|
+
else
|
14
|
+
next_item.call(*args, &next_step)
|
15
|
+
end
|
16
|
+
}
|
17
|
+
next_step.call(*args)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def make_stack
|
23
|
+
@stack ||=
|
24
|
+
map do |item|
|
25
|
+
if item.respond_to?(:call)
|
26
|
+
item
|
27
|
+
elsif item.respond_to?(:new)
|
28
|
+
item.new(@config)
|
29
|
+
else
|
30
|
+
raise ArgumentError, "bad entry #{item.inspect} in middleware stack"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
class Woodhouse::MixinRegistry < Woodhouse::Registry
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def classes
|
6
|
+
@classes ||= {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(klass)
|
10
|
+
register_worker klass.name, klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def register_worker(class_name, klass)
|
14
|
+
classes[class_name.to_s] = klass
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](worker)
|
20
|
+
Woodhouse::MixinRegistry.classes[worker.to_s]
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&blk)
|
24
|
+
Woodhouse::MixinRegistry.classes.each &blk
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Woodhouse::NodeConfiguration
|
2
|
+
include Woodhouse::Util
|
3
|
+
|
4
|
+
attr_accessor :registry, :server_info, :runner_type, :dispatcher_type, :logger, :default_threads
|
5
|
+
attr_accessor :dispatcher_middleware, :runner_middleware
|
6
|
+
attr_accessor :triggers
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
self.default_threads = 1
|
10
|
+
self.dispatcher_middleware = Woodhouse::MiddlewareStack.new(self)
|
11
|
+
self.runner_middleware = Woodhouse::MiddlewareStack.new(self)
|
12
|
+
self.server_info = {}
|
13
|
+
self.triggers = Woodhouse::TriggerSet.new
|
14
|
+
yield self if block_given?
|
15
|
+
end
|
16
|
+
|
17
|
+
def at(event_name, &blk)
|
18
|
+
triggers.add(event_name, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
def dispatcher
|
22
|
+
@dispatcher ||= dispatcher_type.new(self)
|
23
|
+
end
|
24
|
+
|
25
|
+
def dispatcher_type=(value)
|
26
|
+
if value.respond_to?(:to_sym)
|
27
|
+
value = lookup_key(value, :Dispatcher)
|
28
|
+
end
|
29
|
+
@dispatcher = nil
|
30
|
+
@dispatcher_type = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def runner_type=(value)
|
34
|
+
if value.respond_to?(:to_sym)
|
35
|
+
value = lookup_key(value, :Runner)
|
36
|
+
end
|
37
|
+
@dispatcher = nil
|
38
|
+
@runner_type = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def server_info=(hash)
|
42
|
+
@server_info = hash ? symbolize_keys(hash) : {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def extension(name, opts = {}, &blk)
|
46
|
+
Woodhouse::Extension.install_extension(name, self, opts, &blk)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def lookup_key(key, namespace)
|
52
|
+
const = Woodhouse.const_get("#{namespace}s").const_get("#{camelize(key.to_s)}#{namespace}")
|
53
|
+
unless const
|
54
|
+
raise NameError, "couldn't find Woodhouse::#{namespace}s::#{camelize(key.to_s)}#{namespace} (from #{key})"
|
55
|
+
end
|
56
|
+
const
|
57
|
+
end
|
58
|
+
|
59
|
+
def symbolize_keys(hash)
|
60
|
+
hash.inject({}){|h,(k,v)|
|
61
|
+
h[k.to_sym] = v
|
62
|
+
h
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
# TODO: detect defaults based on platform
|
67
|
+
def self.default
|
68
|
+
new do |config|
|
69
|
+
config.registry = Woodhouse::MixinRegistry.new
|
70
|
+
config.server_info = nil
|
71
|
+
config.runner_type = Woodhouse::Runners.guess
|
72
|
+
config.dispatcher_type = :local
|
73
|
+
config.logger = Logger.new("/dev/null")
|
74
|
+
config.dispatcher_middleware << Woodhouse::Middleware::LogDispatch
|
75
|
+
config.runner_middleware << Woodhouse::Middleware::LogJobs
|
76
|
+
config.runner_middleware << Woodhouse::Middleware::AssignLogger
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# TODO: take arguments. Also consider using thor.
|
2
|
+
class Woodhouse::Process
|
3
|
+
|
4
|
+
def initialize(keyw = {})
|
5
|
+
@server = keyw[:server] || build_default_server(keyw)
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute
|
9
|
+
# Borrowed this from sidekiq. https://github.com/mperham/sidekiq/blob/master/lib/sidekiq/cli.rb
|
10
|
+
trap "INT" do
|
11
|
+
Thread.main.raise Interrupt
|
12
|
+
end
|
13
|
+
|
14
|
+
trap "TERM" do
|
15
|
+
Thread.main.raise Interrupt
|
16
|
+
end
|
17
|
+
|
18
|
+
begin
|
19
|
+
@server.start!
|
20
|
+
puts "Woodhouse serving as of #{Time.now}. Ctrl-C to stop."
|
21
|
+
@server.wait(:shutdown)
|
22
|
+
rescue Interrupt
|
23
|
+
puts "Shutting down."
|
24
|
+
@server.shutdown!
|
25
|
+
@server.wait(:shutdown)
|
26
|
+
ensure
|
27
|
+
@server.terminate
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def build_default_server(keyw)
|
35
|
+
Woodhouse::Server.new(
|
36
|
+
:layout => keyw[:layout] || Woodhouse.global_layout,
|
37
|
+
:node => keyw[:node] || :default
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Woodhouse
|
2
|
+
|
3
|
+
class QueueCriteria
|
4
|
+
attr_reader :criteria
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
if opts.kind_of?(self.class)
|
8
|
+
opts = opts.criteria
|
9
|
+
end
|
10
|
+
unless opts.nil?
|
11
|
+
@criteria = stringify_values(opts).freeze
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
@criteria == other.criteria
|
17
|
+
end
|
18
|
+
|
19
|
+
def describe
|
20
|
+
@criteria.inspect
|
21
|
+
end
|
22
|
+
|
23
|
+
def amqp_headers
|
24
|
+
# TODO: needs to be smarter
|
25
|
+
@criteria ? @criteria.merge('x-match' => 'all') : {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def queue_key
|
29
|
+
@criteria ? @criteria.map{|k,v|
|
30
|
+
"#{k.downcase}_#{v.downcase}"
|
31
|
+
}.join("_") : ""
|
32
|
+
end
|
33
|
+
|
34
|
+
def matches?(args)
|
35
|
+
return true if @criteria.nil?
|
36
|
+
@criteria.all? do |key, val|
|
37
|
+
args[key] == val
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def stringify_values(hash)
|
44
|
+
hash.inject({}) {|h,(k,v)|
|
45
|
+
h[k.to_s] = v.to_s
|
46
|
+
h
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
if defined?(Rails::Railtie)
|
2
|
+
module Woodhouse::RailsExtensions
|
3
|
+
def layout(&blk)
|
4
|
+
unless @delay_finished
|
5
|
+
@delayed_layout = blk
|
6
|
+
else
|
7
|
+
super
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def finish_loading_layout!
|
12
|
+
@delay_finished = true
|
13
|
+
if @delayed_layout
|
14
|
+
layout &@delayed_layout
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Woodhouse.extend Woodhouse::RailsExtensions
|
20
|
+
|
21
|
+
class Woodhouse::Rails < Rails::Engine
|
22
|
+
# config.autoload_paths << Rails.root.join("app/workers")
|
23
|
+
|
24
|
+
initializer 'woodhouse' do
|
25
|
+
config_paths = %w[woodhouse.yml workling.yml].map{|file|
|
26
|
+
Rails.root.join("config/" + file)
|
27
|
+
}
|
28
|
+
# Preload everything in app/workers so default layout includes them
|
29
|
+
Rails.root.join("app/workers").tap do |workers|
|
30
|
+
Pathname.glob(workers.join("**/*.rb")).each do |worker_path|
|
31
|
+
worker_path.relative_path_from(workers).basename(".rb").to_s.camelize.constantize
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# Set up reasonable defaults
|
35
|
+
Woodhouse.configure do |config|
|
36
|
+
config.logger = ::Rails.logger
|
37
|
+
config_paths.each do |path|
|
38
|
+
if File.exist?(path)
|
39
|
+
config.server_info = YAML.load(File.read(path))[::Rails.env]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
Woodhouse.finish_loading_layout!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'woodhouse'
|
2
|
+
|
3
|
+
ActiveSupport::Dependencies.autoload_paths << RAILS_ROOT + "/app/workers"
|
4
|
+
|
5
|
+
Woodhouse.configure do |config|
|
6
|
+
config_paths = %w[woodhouse.yml workling.yml].map{|file|
|
7
|
+
RAILS_ROOT + "/config/" + file
|
8
|
+
}
|
9
|
+
config.logger = ::Rails.logger
|
10
|
+
if ::Rails.env =~ /development|test/
|
11
|
+
config.dispatcher_type = :local
|
12
|
+
else
|
13
|
+
config.dispatcher_type = :bunny
|
14
|
+
end
|
15
|
+
config_paths.each do |path|
|
16
|
+
if File.exist?(path)
|
17
|
+
config.server_info = YAML.load(File.read(path))[::Rails.env]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
config.runner_type = Woodhouse::Runners.guess
|
21
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Woodhouse::Registry
|
2
|
+
include Woodhouse::Util
|
3
|
+
|
4
|
+
def [](worker)
|
5
|
+
raise NotImplementedError, "subclass Woodhouse::Registry and override #[]"
|
6
|
+
end
|
7
|
+
|
8
|
+
def each
|
9
|
+
raise NotImplementedError, "subclass Woodhouse::Registry and override #each"
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# The abstract base class for actors in charge of finding and running jobs
|
3
|
+
# of a given type. Runners will be allocated for each Woodhouse::Layout::Worker
|
4
|
+
# in a layout. Woodhouse::Layout::Worker#threads indicates how many Runners should
|
5
|
+
# be spawned for each job type.
|
6
|
+
#
|
7
|
+
# The lifecycle of a Runner is to be created by Woodhouse::Scheduler::WorkerSet,
|
8
|
+
# and to automatically begin subscribing as soon as it is initialized. At some
|
9
|
+
# point, the actor will receive the +spin_down+ message, at which case it must
|
10
|
+
# cease all work and return from +subscribe+.
|
11
|
+
#
|
12
|
+
# Whenever a Runner receives a job on its queue, it should convert it into a
|
13
|
+
# Workling::Job and pass it to +service_job+ after confirming with
|
14
|
+
# +can_service_job?+ that this is an appropriate job for this queue.
|
15
|
+
#
|
16
|
+
# Runners should always subscribe to queues in ack mode. Messages should be
|
17
|
+
# acked after they finish, and rejected if the job is inappropriate for this
|
18
|
+
# worker or if it raises an exception.
|
19
|
+
#
|
20
|
+
# TODO: document in more detail the contract between Runner and Dispatcher over
|
21
|
+
# AMQP exchanges, and how Woodhouse uses AMQP to distribute jobs.
|
22
|
+
#
|
23
|
+
class Woodhouse::Runner
|
24
|
+
include Woodhouse::Util
|
25
|
+
include Celluloid
|
26
|
+
|
27
|
+
def initialize(worker, config)
|
28
|
+
@worker = worker
|
29
|
+
@config = config
|
30
|
+
@config.logger.debug "Thread for #{@worker.describe} ready and waiting for jobs"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Implement this in a subclass. When this message is received by an actor, it should
|
34
|
+
# finish whatever job it is currently doing, gracefully disconnect from AMQP, and
|
35
|
+
# stop the subscribe loop.
|
36
|
+
def spin_down
|
37
|
+
raise NotImplementedError, "implement #spin_down in a subclass of Woodhouse::Runner"
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Implement this in a subclass. When this message is received by an actor, it should
|
43
|
+
# connect to AMQP and start pulling jobs off the queue. This method should not finish
|
44
|
+
# until spin_down is called.
|
45
|
+
def subscribe # :doc:
|
46
|
+
raise NotImplementedError, "implement #subscribe in a subclass of Woodhouse::Runner"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns +true+ if the Job's arguments match this worker's QueueCriteria, else +false+.
|
50
|
+
def can_service_job?(job) # :doc:
|
51
|
+
@worker.accepts_job?(job)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Executes a Job. See Woodhouse::JobExecution.
|
55
|
+
def service_job(job) # :doc:
|
56
|
+
@config.logger.debug "Servicing job for #{@worker.describe}"
|
57
|
+
Woodhouse::JobExecution.new(@config, job).execute
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
|
3
|
+
class Woodhouse::Runners::BunnyRunner < Woodhouse::Runner
|
4
|
+
include Celluloid
|
5
|
+
|
6
|
+
def subscribe
|
7
|
+
bunny = Bunny.new(@config.server_info)
|
8
|
+
bunny.start
|
9
|
+
channel = bunny.create_channel
|
10
|
+
channel.prefetch(1)
|
11
|
+
queue = channel.queue(@worker.queue_name)
|
12
|
+
exchange = channel.exchange(@worker.exchange_name, :type => :headers)
|
13
|
+
queue.bind(exchange, :arguments => @worker.criteria.amqp_headers)
|
14
|
+
worker = Celluloid.current_actor
|
15
|
+
queue.subscribe(:ack => true, :block => false) do |delivery, props, payload|
|
16
|
+
begin
|
17
|
+
job = make_job(props, payload)
|
18
|
+
if can_service_job?(job)
|
19
|
+
if service_job(job)
|
20
|
+
channel.acknowledge(delivery.delivery_tag, false)
|
21
|
+
else
|
22
|
+
channel.reject(delivery.delivery_tag, false)
|
23
|
+
end
|
24
|
+
else
|
25
|
+
@config.logger.error("Cannot service job #{job.describe} in queue for #{@worker.describe}")
|
26
|
+
channel.reject(delivery.delivery_tag, false)
|
27
|
+
end
|
28
|
+
rescue => err
|
29
|
+
begin
|
30
|
+
@config.logger.error("Error bubbled up out of worker. This shouldn't happen. #{err.message}")
|
31
|
+
err.backtrace.each do |btr|
|
32
|
+
@config.logger.error(" #{btr}")
|
33
|
+
end
|
34
|
+
# Don't risk grabbing this job again.
|
35
|
+
channel.reject(delivery.delivery_tag, false)
|
36
|
+
ensure
|
37
|
+
worker.bail_out(err)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
wait :spin_down
|
42
|
+
end
|
43
|
+
|
44
|
+
def bail_out(err)
|
45
|
+
raise Woodhouse::BailOut, "#{err.class}: #{err.message}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def spin_down
|
49
|
+
signal :spin_down
|
50
|
+
end
|
51
|
+
|
52
|
+
def make_job(properties, payload)
|
53
|
+
Woodhouse::Job.new(@worker.worker_class_name, @worker.job_method) do |job|
|
54
|
+
args = properties.headers
|
55
|
+
job.arguments = args
|
56
|
+
job.payload = payload
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|