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
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2008 play/type GmbH
|
2
|
+
Copyright (c) 2012 CrowdCompass, Inc.
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/PROGRESS-NOTES.txt
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# Woodhouse
|
2
|
+
|
3
|
+
[<img src="https://secure.travis-ci.org/mboeh/woodhouse.png?branch=master" alt="Build Status" />](http://travis-ci.org/mboeh/woodhouse)
|
4
|
+
|
5
|
+
An AMQP-based background worker system for Ruby designed to make managing heterogenous tasks relatively easy.
|
6
|
+
|
7
|
+
The use case for Woodhouse is for reliable and sane performance in situations where jobs on a single queue may vary significantly
|
8
|
+
in length. The goal is to permit large numbers of quick jobs to be serviced even when many slow jobs are in the queue. A secondary
|
9
|
+
goal is to provide a sane way for jobs on a given queue to be given special priority or dispatched to a server more suited to them.
|
10
|
+
|
11
|
+
Woodhouse 0.0.x is production-ready for Rails 2 and Ruby 1.8, while 0.1.x is in active development for Ruby 1.9.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Rails
|
16
|
+
|
17
|
+
Add
|
18
|
+
|
19
|
+
gem 'woodhouse', github: 'mboeh/woodhouse'
|
20
|
+
|
21
|
+
to your Gemfile.
|
22
|
+
|
23
|
+
Run
|
24
|
+
|
25
|
+
% rails generate woodhouse
|
26
|
+
|
27
|
+
to create script/woodhouse and config/initializers/woodhouse.rb.
|
28
|
+
|
29
|
+
### Basic Usage
|
30
|
+
|
31
|
+
The simplest way to set up a worker class is to include Woodhouse::Worker and define public methods.
|
32
|
+
|
33
|
+
class IsisWorker
|
34
|
+
include Woodhouse::Worker
|
35
|
+
|
36
|
+
def pam_gossip(job)
|
37
|
+
puts "Pam gossips about #{job[:who]}."
|
38
|
+
end
|
39
|
+
|
40
|
+
def sterling_insult(job)
|
41
|
+
puts "Sterling insults #{job[:who]}."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
Jobs are dispatched asynchronously to a worker by adding `async_` to the method name:
|
46
|
+
|
47
|
+
IsisWorker.async_pam_gossip :who => "Cyril"
|
48
|
+
|
49
|
+
Woodhouse jobs always take a hash of arguments. The worker receives a Woodhouse::Job, which acts like a hash
|
50
|
+
but also supplies additional functionality.
|
51
|
+
|
52
|
+
### Dispatchers
|
53
|
+
|
54
|
+
The dispatcher used for sending out jobs can be set in the Woodhouse config block:
|
55
|
+
|
56
|
+
Woodhouse.configure do |woodhouse|
|
57
|
+
woodhouse.dispatcher_type = :local # :local_pool | :amqp
|
58
|
+
end
|
59
|
+
|
60
|
+
Calling the `async` version of a job method sends it to the currently configured dispatcher. The default dispatcher
|
61
|
+
type is `:local`, which simply executes the job synchronously (although still passing it through middleware; see below).
|
62
|
+
|
63
|
+
If you want `girl_friday` style in-process threaded backgrounding, you can get that by selecting the `:local_pool`
|
64
|
+
dispatcher.
|
65
|
+
|
66
|
+
Finally, if you want to run your jobs in a background process, you'll need to set up the `:amqp` dispatcher. This will
|
67
|
+
use either the Hot Bunnies library (on JRuby) or the Bunny library (on all other Ruby engines). Bunny is suitable for
|
68
|
+
dispatch but can be a little bit CPU-hungry in the background process. Hot Bunnies works great for both. You don't have
|
69
|
+
to use the same Ruby version for your background process as for the dispatching application -- we use Woodhouse in production
|
70
|
+
with a JRuby background process and MRI frontend processes.
|
71
|
+
|
72
|
+
You'll also need to have RabbitMQ running. If it's running with the default (open) permissions on the local server, you don't
|
73
|
+
need to configure it at all. Otherwise, you'll have to set the server connection info. You have two options for this. On Rails,
|
74
|
+
you can create a config/woodhouse.yml file, formatted similar to config/database.yml:
|
75
|
+
|
76
|
+
production:
|
77
|
+
host: myrabbitmq.server.local
|
78
|
+
vhost: /some-vhost
|
79
|
+
|
80
|
+
(The parameters accepted here are the same used for Bunny.connect; I promise to document them here soon.)
|
81
|
+
|
82
|
+
Otherwise, you can do it in the Woodhouse config block:
|
83
|
+
|
84
|
+
Woodhouse.configure do |woodhouse|
|
85
|
+
woodhouse.server_info = { :host => "myrabbitmq.server.local" }
|
86
|
+
end
|
87
|
+
|
88
|
+
### Running The Background Process
|
89
|
+
|
90
|
+
All you have to do is run `script/woodhouse`. It'll load your Rails environment and start the server process. It responds to QUIT
|
91
|
+
and INT signals correctly; I'm working on seeing if I can get it to restart worker processes with HUP and to dump/load the current
|
92
|
+
layout with USR1/USR2.
|
93
|
+
|
94
|
+
`script/woodhouse` logs job execution and results to `log/woodhouse.log`.
|
95
|
+
|
96
|
+
### Performance Errata
|
97
|
+
|
98
|
+
If you're using JRuby with a large application, I've found that the JVM's permanent generation can get exhausted. If you have
|
99
|
+
plenty of heap but still get GC overhead errors, try bumping up the PermGen by including this on the JRuby command line:
|
100
|
+
|
101
|
+
-J-XX:MaxPermSize=128m
|
102
|
+
|
103
|
+
Performance will generally be better with the `--server` flag:
|
104
|
+
|
105
|
+
--server
|
106
|
+
|
107
|
+
A lot of the jobs I run tend to allocate and dispose a lot of memory very quickly, and Woodhouse will be a long-running process.
|
108
|
+
I've gotten good results from enabling aggressive heap tuning:
|
109
|
+
|
110
|
+
-J-XX:+AggressiveHeap
|
111
|
+
|
112
|
+
## Features
|
113
|
+
|
114
|
+
* Configurable worker sets per server
|
115
|
+
* Configurable number of threads per worker
|
116
|
+
* Segmenting a single queue among multiple workers based on job characteristics (using AMQP header exchanges)
|
117
|
+
* Progress reporting on jobs
|
118
|
+
* New Relic background job reporting
|
119
|
+
* Job dispatch and execution middleware stacks
|
120
|
+
|
121
|
+
## Upcoming
|
122
|
+
|
123
|
+
* Live reconfiguration of workers -- add or remove workers across one or more nodes without restarting
|
124
|
+
* Persistent configuration changes -- configuration changes saved to a data store and kept across deploys
|
125
|
+
* Watchdog/status workers on every node
|
126
|
+
* Web interface
|
127
|
+
|
128
|
+
## To Do
|
129
|
+
|
130
|
+
* Examples and guides
|
131
|
+
* More documentation
|
132
|
+
* Watchdog system
|
133
|
+
|
134
|
+
## Supported Versions
|
135
|
+
|
136
|
+
### woodhouse 0.1.x
|
137
|
+
|
138
|
+
* bunny 0.9.x, RabbitMQ 2.x or later
|
139
|
+
* ruby 1.9
|
140
|
+
* MRI, JRuby, Rubinius 2
|
141
|
+
|
142
|
+
### woodhouse 0.0.x
|
143
|
+
|
144
|
+
* ruby 1.8
|
145
|
+
* MRI, JRuby, Rubinius
|
146
|
+
|
147
|
+
### Acknowledgements
|
148
|
+
|
149
|
+
Woodhouse originated in a substantially modified version of the Workling background worker system, although all code has since
|
150
|
+
been replaced.
|
151
|
+
|
152
|
+
This library was developed for [CrowdCompass](http://crowdcompass.com) and was released as open source with their permission.
|
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'bundler'
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
require 'spec/rake/spectask'
|
8
|
+
namespace :spec do
|
9
|
+
|
10
|
+
Spec::Rake::SpecTask.new(:server) do |t|
|
11
|
+
t.spec_files = FileList["spec/**/*_spec.rb"] - FileList["spec/integration/*_spec.rb"]
|
12
|
+
end
|
13
|
+
|
14
|
+
Spec::Rake::SpecTask.new(:client) do |t|
|
15
|
+
t.spec_files = %w[spec/layout_spec.rb spec/middleware_stack_spec.rb spec/mixin_registry_spec.rb]
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
# Full server specs are supported on Ruby 1.9 or JRuby.
|
21
|
+
if RUBY_VERSION.to_f >= 1.9 or %w[jruby rbx].include?(RUBY_ENGINE)
|
22
|
+
task :spec => "spec:server"
|
23
|
+
else
|
24
|
+
task :spec => "spec:client"
|
25
|
+
end
|
26
|
+
|
27
|
+
task :default => :spec
|
28
|
+
|
29
|
+
if ENV['RDOC']
|
30
|
+
require 'rdoc/task'
|
31
|
+
Rake::RDocTask.new(:rdoc) do |t|
|
32
|
+
t.main = "README.rdoc"
|
33
|
+
t.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
34
|
+
end
|
35
|
+
end
|
data/THOUGHTS
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
How I want Ganymede to work:
|
2
|
+
|
3
|
+
# app/workers/foo_bar_worker.rb
|
4
|
+
class FooBarWorker < Ganymede::Worker
|
5
|
+
|
6
|
+
def foo(options)
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
def bar(options)
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
# somewhere that jobs are dispatched
|
17
|
+
|
18
|
+
def do_a_thing(because)
|
19
|
+
@because = because
|
20
|
+
FooBarWorker.foo(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_ganymede
|
24
|
+
{
|
25
|
+
:id => id,
|
26
|
+
:class => self.class.name,
|
27
|
+
:event_name => self.event.name,
|
28
|
+
:size => estimate_job_size,
|
29
|
+
:trigger => @because
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
# config/initializers/ganymede.rb
|
34
|
+
|
35
|
+
# Default configuration: all workers listen to all jobs, one thread per job,
|
36
|
+
# no filtering.
|
37
|
+
Ganymede.configure do |config|
|
38
|
+
config.layout do |layout|
|
39
|
+
# layout.no_default_node! if you do not want a default node type
|
40
|
+
layout.node :bigjobs do |bigjobs|
|
41
|
+
bigjobs.worker :FooBarWorker, :only => { :size => "big" }, :threads => 2
|
42
|
+
bigjobs.worker :FooBarWorker, :only => { :size => "big", :trigger => "scheduled" }, :threads => 1
|
43
|
+
end
|
44
|
+
layout.default.worker :FooBarWorker, :only => { :size => "small", :trigger => "scheduled" }, :threads => 2
|
45
|
+
layout.default.worker :FooBarWorker, :only => { :size => "small", :trigger => "on-demand" }, :threads => 4
|
46
|
+
layout.default.worker :FooBarWorker, :only => { :size => "small", :trigger => "requested" }, :threads => 1
|
47
|
+
end
|
48
|
+
# config.middleware_out << Ganymede::Middleware::AutoSplatObjects
|
49
|
+
end
|
50
|
+
|
51
|
+
class Ganymede::Middleware::AutoSplatObjects
|
52
|
+
|
53
|
+
def call(*opts)
|
54
|
+
if opts.length == 1 and !opts.first.kind_of?(Hash)
|
55
|
+
if opts.respond_to?(:to_ganymede)
|
56
|
+
opts = opts.to_ganymede
|
57
|
+
end
|
58
|
+
end
|
59
|
+
yield opts
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# equivalent to:
|
65
|
+
layout = Ganymede::Layout.new
|
66
|
+
default = Ganymede::Layout::Node.new(:default)
|
67
|
+
default.default_configuration!
|
68
|
+
default.add_worker Ganymede::Layout::Worker.new(:FooBarWorker, :foo, :only => { :size => "small", :trigger => "scheduled" }, :threads => 2)
|
69
|
+
# ...
|
70
|
+
layout.add_node default
|
71
|
+
bigjobs = Ganymede::Layout::Node.new(:bigjobs)
|
72
|
+
bigjobs.add_worker Ganymede::Layout::Worker.new(:FooBarWorker, :foo, :only => { :size => "big", :trigger => "scheduled" }, :threads => 1)
|
73
|
+
|
74
|
+
server = Ganymede::Server.new(layout, :default)
|
75
|
+
server.start # FIAT LUX
|
76
|
+
|
77
|
+
# ... later ...
|
78
|
+
# ... We really need a special worker just allocated for HugeEvent
|
79
|
+
|
80
|
+
default.add_worker Ganymede::Worker.new(:FooBarWorker, :foo, :only => { :event_name => "HugeEvent" }, :threads => 1
|
81
|
+
|
82
|
+
# Loads the new layout and starts up new workers as required
|
83
|
+
server.layout = layout
|
84
|
+
server.reload
|
@@ -0,0 +1,9 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require File.dirname(__FILE__) + '/../config/environment'
|
3
|
+
|
4
|
+
logg = Logger.new(File.dirname(__FILE__) + "/../log/woodhouse.log")
|
5
|
+
logg.level = Logger::DEBUG
|
6
|
+
Woodhouse.global_configuration.logger = logg
|
7
|
+
Celluloid.logger = logg
|
8
|
+
|
9
|
+
Woodhouse::Process.new.execute
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Woodhouse.configure do |woodhouse|
|
2
|
+
woodhouse.runner_middleware << Woodhouse::Middleware::AirbrakeExceptions
|
3
|
+
end
|
4
|
+
|
5
|
+
Woodhouse.layout do |layout|
|
6
|
+
layout.node(:default) do |node|
|
7
|
+
node.all_workers
|
8
|
+
node.remove :ImportWorker
|
9
|
+
node.add :ImportWorker, :threads => 2, :only => { :format => "csv" }
|
10
|
+
node.add :ImportWorker, :threads => 3, :only => { :format => "xml" }
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class WoodhouseGenerator < Rails::Generators::Base
|
2
|
+
desc "Creates initializer and script files for Woodhouse."
|
3
|
+
|
4
|
+
def create_initializer
|
5
|
+
initializer "woodhouse.rb", <<-EOF
|
6
|
+
Woodhouse.configure do |woodhouse|
|
7
|
+
# woodhouse.dispatcher_type = :amqp
|
8
|
+
# woodhouse.extension :progress
|
9
|
+
# woodhouse.extension :new_relic
|
10
|
+
end
|
11
|
+
|
12
|
+
Woodhouse.layout do |layout|
|
13
|
+
layout.node(:default) do |node|
|
14
|
+
node.all_workers
|
15
|
+
end
|
16
|
+
end
|
17
|
+
EOF
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_script
|
21
|
+
create_file "script/woodhouse", <<-EOF
|
22
|
+
#!/usr/bin/env ruby
|
23
|
+
require File.expand_path(File.dirname(__FILE__) + '/../config/environment')
|
24
|
+
|
25
|
+
logg = Logger.new(File.dirname(__FILE__) + "/../log/woodhouse.log")
|
26
|
+
logg.level = Logger::DEBUG
|
27
|
+
logg.formatter = Logger::Formatter.new
|
28
|
+
|
29
|
+
Celluloid.logger = logg
|
30
|
+
Woodhouse.global_configuration.logger = logg
|
31
|
+
|
32
|
+
Woodhouse.global_configuration.dispatcher_type = :amqp
|
33
|
+
|
34
|
+
Woodhouse::Process.new.execute
|
35
|
+
EOF
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Woodhouse::Dispatcher
|
2
|
+
|
3
|
+
def initialize(config, opts = {}, &blk)
|
4
|
+
@config = config
|
5
|
+
after_initialize(config, opts = {}, &blk)
|
6
|
+
end
|
7
|
+
|
8
|
+
def dispatch(class_name, job_method, arguments)
|
9
|
+
dispatch_job Woodhouse::Job.new(class_name, job_method, arguments)
|
10
|
+
end
|
11
|
+
|
12
|
+
def dispatch_job(job)
|
13
|
+
@config.dispatcher_middleware.call(job) {|job|
|
14
|
+
deliver_job(job)
|
15
|
+
}
|
16
|
+
job
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_job(job, data = {})
|
20
|
+
deliver_job_update(job, data)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def after_initialize
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
def deliver_job(job)
|
30
|
+
raise NotImplementedError, "implement #deliver_job in a subclass of Woodhouse::Dispatcher"
|
31
|
+
end
|
32
|
+
|
33
|
+
def deliver_job_update(job, data)
|
34
|
+
raise NotImplementedError, "implement #deliver_job_update in a subclass of Woodhouse::Dispatcher"
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'bunny'
|
2
|
+
require 'connection_pool'
|
3
|
+
require 'woodhouse/dispatchers/common_amqp_dispatcher'
|
4
|
+
|
5
|
+
class Woodhouse::Dispatchers::BunnyDispatcher < Woodhouse::Dispatchers::CommonAmqpDispatcher
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
super
|
9
|
+
@pool = new_pool
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def publish_job(job, exchange)
|
15
|
+
exchange.publish(job.payload, :headers => job.arguments)
|
16
|
+
end
|
17
|
+
|
18
|
+
def run
|
19
|
+
retried = false
|
20
|
+
@pool.with do |conn|
|
21
|
+
yield conn
|
22
|
+
end
|
23
|
+
rescue Bunny::ClientTimeout
|
24
|
+
if retried
|
25
|
+
raise
|
26
|
+
else
|
27
|
+
new_pool!
|
28
|
+
retried = true
|
29
|
+
retry
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def new_pool!
|
36
|
+
@pool = new_pool
|
37
|
+
end
|
38
|
+
|
39
|
+
def new_pool
|
40
|
+
@bunny.stop if @bunny
|
41
|
+
|
42
|
+
bunny = @bunny = Bunny.new(@config.server_info || {})
|
43
|
+
@bunny.start
|
44
|
+
|
45
|
+
ConnectionPool.new { bunny.create_channel }
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Provides common behavior shared by the Bunny and HotBunnies dispatchers.
|
2
|
+
class Woodhouse::Dispatchers::CommonAmqpDispatcher < Woodhouse::Dispatcher
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
# Yields an AMQP channel to the block, doing any error handling or synchronization
|
7
|
+
# necessary.
|
8
|
+
def run(&blk)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
|
12
|
+
def deliver_job(job)
|
13
|
+
run do |client|
|
14
|
+
exchange = client.exchange(job.exchange_name, :type => :headers)
|
15
|
+
publish_job(job, exchange)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def deliver_job_update(job, data)
|
20
|
+
run do |client|
|
21
|
+
exchange = client.exchange("woodhouse.progress", :type => :direct)
|
22
|
+
# establish durable queue to pick up updates
|
23
|
+
client.queue(job.job_id, :durable => true).bind(exchange, :routing_key => job.job_id)
|
24
|
+
exchange.publish(data.to_json, :routing_key => job.job_id)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# A Dispatcher implementation that uses hot_bunnies, a JRuby AMQP client using the
|
3
|
+
# Java client for RabbitMQ. This class can be loaded if hot_bunnies is not
|
4
|
+
# available, but it will fail upon initialization. If you want to use this
|
5
|
+
# runner (it's currently the only one that works very well), make sure to
|
6
|
+
# add
|
7
|
+
#
|
8
|
+
# gem 'hot_bunnies'
|
9
|
+
#
|
10
|
+
# to your Gemfile.
|
11
|
+
#
|
12
|
+
|
13
|
+
require 'woodhouse/dispatchers/common_amqp_dispatcher'
|
14
|
+
|
15
|
+
class Woodhouse::Dispatchers::HotBunniesDispatcher < Woodhouse::Dispatchers::CommonAmqpDispatcher
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'hot_bunnies'
|
19
|
+
rescue LoadError => err
|
20
|
+
define_method(:initialize) {|*args|
|
21
|
+
raise err
|
22
|
+
}
|
23
|
+
else
|
24
|
+
def initialize(config)
|
25
|
+
super
|
26
|
+
new_connection
|
27
|
+
@mutex = Mutex.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def run
|
34
|
+
@mutex.synchronize do
|
35
|
+
yield @channel
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def publish_job(job, exchange)
|
40
|
+
exchange.publish(job.payload, :properties => { :headers => job.arguments })
|
41
|
+
end
|
42
|
+
|
43
|
+
def new_connection
|
44
|
+
@connection = HotBunnies.connect(@config.server_info)
|
45
|
+
@channel = @connection.create_channel
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Woodhouse::Dispatchers::LocalDispatcher < Woodhouse::Dispatcher
|
2
|
+
|
3
|
+
private
|
4
|
+
|
5
|
+
def deliver_job(job)
|
6
|
+
Woodhouse::JobExecution.new(@config, job).execute
|
7
|
+
end
|
8
|
+
|
9
|
+
def deliver_job_update(job, data)
|
10
|
+
@config.logger.info "[Woodhouse job update] #{job.job_id} -- #{data.inspect}"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Woodhouse::Dispatchers::LocalPoolDispatcher < Woodhouse::Dispatcher
|
2
|
+
|
3
|
+
class Worker
|
4
|
+
include Celluloid
|
5
|
+
|
6
|
+
def execute(executor)
|
7
|
+
executor.execute
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def after_initialize(config, opts = {}, &blk)
|
14
|
+
@pool = Worker.pool(size: opts[:size] || 10)
|
15
|
+
end
|
16
|
+
|
17
|
+
def deliver_job(job)
|
18
|
+
@pool.async.execute Woodhouse::JobExecution.new(@config, job)
|
19
|
+
end
|
20
|
+
|
21
|
+
def deliver_job_update(job, data)
|
22
|
+
@config.logger.info "[Woodhouse job update] #{job.job_id} -- #{data.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Woodhouse::Dispatchers
|
2
|
+
|
3
|
+
def self.default_amqp_dispatcher
|
4
|
+
if RUBY_ENGINE =~ /jruby/
|
5
|
+
Woodhouse::Dispatchers::HotBunniesDispatcher
|
6
|
+
else
|
7
|
+
Woodhouse::Dispatchers::BunnyDispatcher
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'woodhouse/dispatcher'
|
14
|
+
require 'woodhouse/dispatchers/local_dispatcher'
|
15
|
+
require 'woodhouse/dispatchers/bunny_dispatcher'
|
16
|
+
require 'woodhouse/dispatchers/hot_bunnies_dispatcher'
|
17
|
+
require 'woodhouse/dispatchers/local_pool_dispatcher'
|
18
|
+
|
19
|
+
Woodhouse::Dispatchers::AmqpDispatcher = Woodhouse::Dispatchers.default_amqp_dispatcher
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# Implements a very basic registry for Woodhouse extensions. This is a Class
|
2
|
+
# rather than a Module because it will eventually be used to provide a more
|
3
|
+
# structured approach than the one Woodhouse::Progress uses.
|
4
|
+
class Woodhouse::Extension
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
attr_accessor :registry
|
9
|
+
|
10
|
+
def register(name, extension)
|
11
|
+
registry[name] = extension
|
12
|
+
end
|
13
|
+
|
14
|
+
def install_extension(name, configuration, opts = {}, &blk)
|
15
|
+
if ext = registry[name]
|
16
|
+
ext.install_extension(configuration, opts, &blk)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
self.registry = {}
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Woodhouse::NewRelic::InstrumentationMiddleware < Woodhouse::Middleware
|
2
|
+
include NewRelic::Agent::Instrumentation::ControllerInstrumentation
|
3
|
+
|
4
|
+
def call(job, worker)
|
5
|
+
perform_action_with_newrelic_trace(:name => job.job_method, :class_name => job.worker_class_name, :params => job.arguments, :category => :task, :path => job.queue_name) do
|
6
|
+
yield job, worker
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'woodhouse'
|
2
|
+
|
3
|
+
module Woodhouse::NewRelic
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def install_extension(configuration, opts = {}, &blk)
|
8
|
+
require 'woodhouse/extensions/new_relic/instrumentation_middleware'
|
9
|
+
configuration.runner_middleware << Woodhouse::NewRelic::InstrumentationMiddleware
|
10
|
+
configuration.at(:server_start) do
|
11
|
+
::NewRelic::Agent.manual_start
|
12
|
+
configuration.logger.info "New Relic agent started."
|
13
|
+
end
|
14
|
+
configuration.at(:server_end) do
|
15
|
+
::NewRelic::Agent.shutdown
|
16
|
+
configuration.logger.info "New Relic agent shut down."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
Woodhouse::Extension.register :new_relic, Woodhouse::NewRelic
|