woodhouse 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/.gitignore +5 -0
  2. data/.travis.yml +5 -0
  3. data/Gemfile +7 -0
  4. data/Guardfile +4 -0
  5. data/MIT-LICENSE +21 -0
  6. data/PROGRESS-NOTES.txt +5 -0
  7. data/README.markdown +152 -0
  8. data/Rakefile +35 -0
  9. data/THOUGHTS +84 -0
  10. data/doc/example/script-woodhouse +9 -0
  11. data/doc/example/woodhouse-initializer.rb +12 -0
  12. data/lib/generators/woodhouse_generator.rb +38 -0
  13. data/lib/woodhouse/dispatcher.rb +37 -0
  14. data/lib/woodhouse/dispatchers/bunny_dispatcher.rb +48 -0
  15. data/lib/woodhouse/dispatchers/common_amqp_dispatcher.rb +28 -0
  16. data/lib/woodhouse/dispatchers/hot_bunnies_dispatcher.rb +48 -0
  17. data/lib/woodhouse/dispatchers/local_dispatcher.rb +13 -0
  18. data/lib/woodhouse/dispatchers/local_pool_dispatcher.rb +25 -0
  19. data/lib/woodhouse/dispatchers.rb +19 -0
  20. data/lib/woodhouse/extension.rb +24 -0
  21. data/lib/woodhouse/extensions/new_relic/instrumentation_middleware.rb +10 -0
  22. data/lib/woodhouse/extensions/new_relic.rb +23 -0
  23. data/lib/woodhouse/extensions/progress.rb +165 -0
  24. data/lib/woodhouse/job.rb +76 -0
  25. data/lib/woodhouse/job_execution.rb +60 -0
  26. data/lib/woodhouse/layout.rb +290 -0
  27. data/lib/woodhouse/layout_builder.rb +55 -0
  28. data/lib/woodhouse/layout_serializer.rb +82 -0
  29. data/lib/woodhouse/middleware/airbrake_exceptions.rb +12 -0
  30. data/lib/woodhouse/middleware/assign_logger.rb +10 -0
  31. data/lib/woodhouse/middleware/log_dispatch.rb +21 -0
  32. data/lib/woodhouse/middleware/log_jobs.rb +22 -0
  33. data/lib/woodhouse/middleware.rb +16 -0
  34. data/lib/woodhouse/middleware_stack.rb +35 -0
  35. data/lib/woodhouse/mixin_registry.rb +27 -0
  36. data/lib/woodhouse/node_configuration.rb +80 -0
  37. data/lib/woodhouse/process.rb +41 -0
  38. data/lib/woodhouse/queue_criteria.rb +52 -0
  39. data/lib/woodhouse/rails.rb +46 -0
  40. data/lib/woodhouse/rails2.rb +21 -0
  41. data/lib/woodhouse/registry.rb +12 -0
  42. data/lib/woodhouse/runner.rb +60 -0
  43. data/lib/woodhouse/runners/bunny_runner.rb +60 -0
  44. data/lib/woodhouse/runners/dummy_runner.rb +11 -0
  45. data/lib/woodhouse/runners/hot_bunnies_runner.rb +79 -0
  46. data/lib/woodhouse/runners.rb +16 -0
  47. data/lib/woodhouse/scheduler.rb +113 -0
  48. data/lib/woodhouse/server.rb +80 -0
  49. data/lib/woodhouse/trigger_set.rb +19 -0
  50. data/lib/woodhouse/version.rb +3 -0
  51. data/lib/woodhouse/worker.rb +86 -0
  52. data/lib/woodhouse.rb +99 -0
  53. data/spec/integration/bunny_worker_process_spec.rb +32 -0
  54. data/spec/layout_builder_spec.rb +55 -0
  55. data/spec/layout_spec.rb +143 -0
  56. data/spec/middleware_stack_spec.rb +56 -0
  57. data/spec/mixin_registry_spec.rb +15 -0
  58. data/spec/node_configuration_spec.rb +22 -0
  59. data/spec/progress_spec.rb +40 -0
  60. data/spec/queue_criteria_spec.rb +11 -0
  61. data/spec/scheduler_spec.rb +41 -0
  62. data/spec/server_spec.rb +72 -0
  63. data/spec/shared_contexts.rb +70 -0
  64. data/spec/worker_spec.rb +28 -0
  65. data/woodhouse.gemspec +37 -0
  66. metadata +285 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.swp
2
+ .bundle
3
+ .binstubs
4
+ Gemfile.lock
5
+ html/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - jruby-19mode
5
+ - rbx-19mode
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ platforms :jruby do
4
+ gem 'hot_bunnies'
5
+ end
6
+
7
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,4 @@
1
+ guard 'rspec', :version => 1 do
2
+ watch(%r{spec/.+_spec\.rb})
3
+ watch(%r{lib/(.+?)/(.+)\.rb}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ end
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.
@@ -0,0 +1,5 @@
1
+ # Progress Notifications
2
+
3
+ * Jobs have a _progress_id added to the arguments
4
+ * A direct exchange 'woodhouse.jobs' is created
5
+ * This _progress_id is used as the routing key
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