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.
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