woodhouse 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|