rails-queue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 We're Probably Wrong
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,179 @@
1
+ # rails-queue
2
+
3
+ [![Build Status](https://secure.travis-ci.org/probablywrong/rails-queue.png)](https://travis-ci.org/probablywrong/rails-queue)
4
+
5
+ ## Description
6
+
7
+ It's the `Rails.queue` [from Rails 4](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/queueing.rb), backported to Rails 3.2+ for your Queueing pleasure.
8
+
9
+ > `Rails.queue` is the application's queue. You can push a job onto the queue by:
10
+ >
11
+ > ```
12
+ > Rails.queue.push job
13
+ > ```
14
+ >
15
+ > A job is an object that responds to `#run`. Queue consumers will pop jobs off of the queue and invoke the queue's `run` method.
16
+ >
17
+ > Note that depending on your queue implementation, jobs may not be executed in the same process as they were created in, and are never executed in the same thread as they were created in.
18
+ >
19
+ > If necessary, a queue implementation may need to serialize your job for distribution to another process. The documentation of your queue will specify the requirements for that serialization
20
+
21
+
22
+ ## Requirements
23
+
24
+ This gem requires Rails 3.2+ and has been tested on the following versions:
25
+
26
+ * 3.2.8
27
+ * 3.2.9
28
+
29
+ This gem has been tested against the following Ruby versions:
30
+
31
+ * 1.9.2
32
+ * 1.9.3
33
+
34
+
35
+ ## Installation
36
+
37
+ Add this line to your application's Gemfile:
38
+
39
+ ```ruby
40
+ gem "rails-queue"
41
+ ```
42
+
43
+ And then execute:
44
+
45
+ ```
46
+ $ bundle
47
+ ```
48
+
49
+ Or install it yourself as:
50
+
51
+ ```
52
+ $ gem install rails-queue
53
+ ```
54
+
55
+
56
+ ## Usage
57
+
58
+ The following queueing strategies are provided by default:
59
+
60
+ * `SynchronousQueue`
61
+ * `TestQueue`
62
+
63
+ The following queue consumer strategies are provided by default:
64
+
65
+ * `ThreadedQueueConsumer`
66
+
67
+ The threaded consumer will run jobs in a background thread in development mode or in a VM where running jobs on a thread in production mode makes sense.
68
+
69
+ When the process exits, the consumer pushes a `nil` onto the queue and joins the thread, which will ensure that all jobs are executed before the process finally dies.
70
+
71
+ _Note: In this port, classes that live under the `ActiveSupport::` namespace in Rails 4 have been moved to the `Rails::Queue::` namespace._
72
+
73
+ ### Configuration
74
+
75
+ Rails will now have the following options available:
76
+
77
+ * `config.queue`
78
+ * `config.queue_consumer`
79
+ * `config.action_mailer.queue`
80
+
81
+ These can be customized in `config/application.rb` or by environment:
82
+
83
+ ```ruby
84
+ # config/environments/production.rb
85
+
86
+ # Default the production mode queue to an synchronous queue. You will probably
87
+ # want to replace this with an out-of-process queueing solution.
88
+ config.queue = Rails::Queue::SynchronousQueue.new
89
+
90
+ # You will probably want to change the job queue consumer from the default.
91
+ config.queue_consumer = Rails::Queue::ThreadedQueueConsumer.new
92
+ ```
93
+
94
+
95
+ ### ActionMailer
96
+
97
+ Mail delivery from ActionMailer can be processed asynchronously using the Rails.queue.
98
+
99
+ By default, ActionMailer will use the application default Rails.queue strategy. This can be configured:
100
+
101
+ ```ruby
102
+ # The queue that will be used to deliver the mail. The queue should expect a job
103
+ # that responds to run.
104
+ config.action_mailer.queue = Rails::Queue::SynchronousQueue.new
105
+ ```
106
+
107
+
108
+ ### Testing
109
+
110
+ In test mode, the Rails queue is backed by an Array so that assertions can be made about its contents. The test queue provides a `jobs` method to make assertions about the queue's contents and a `drain` method to drain the queue and run the jobs.
111
+
112
+ Jobs are run in a separate thread to catch mistakes where code assumes that the job is run in the same thread.
113
+
114
+ ```ruby
115
+ # config/environments/test.rb
116
+
117
+ config.queue = Rails::Queue::TestQueue.new
118
+ ```
119
+
120
+ ### Alternate Providers
121
+
122
+ The Rails Queue implementation is designed to be pluggable and you may want to investigate other queue providers, such as:
123
+
124
+ * [resque-rails](https://github.com/jeremy/resque-rails)
125
+ * [Sidekiq](https://github.com/mperham/sidekiq/tree/rails4)
126
+
127
+
128
+ ## Resources
129
+
130
+ The following are some resources from around the Internet which introduce the Rails Queue:
131
+
132
+ _Note: Many of these are, or will be soon, out of date._
133
+
134
+ * [Ruby on Rails 4.0 Release Notes](http://edgeguides.rubyonrails.org/4_0_release_notes.html)
135
+ * [Rails 4.0 Sneak Peek: Queueing](http://reefpoints.dockyard.com/ruby/2012/06/25/rails-4-sneak-peek-queueing.html)
136
+ * [New in Rails 4.0: Rails Queue](http://www.3magine.com/blog/new-in-rails-4-0-rails-queue/)
137
+
138
+
139
+ ## A note on spelling
140
+
141
+ Yes, there are two spelling options. We defer to research provided by [XKCD](http://xkcd.com/853/).
142
+
143
+
144
+ ## Authors
145
+
146
+ This was written/extracted as part of an ongoing yak-shave known as "We're Probably Wrong", a production of:
147
+
148
+ * [@elskwid](https://github.com/elskwid) [![endorse](http://api.coderwall.com/elskwid/endorsecount.png)](http://coderwall.com/elskwid)
149
+ * [@phlipper](https://github.com/phlipper) [![endorse](http://api.coderwall.com/phlipper/endorsecount.png)](http://coderwall.com/phlipper)
150
+
151
+
152
+ ## Contributors
153
+
154
+ Many thanks go to the following who have contributed to making this gem even better:
155
+
156
+ * **[@rails](https://github.com/rails)**
157
+ * for making this all possible in the first place
158
+
159
+
160
+ ## Contributing
161
+
162
+ 1. [Fork it](https://github.com/phlipper/rails-four-queueing/fork_select)
163
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
164
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
165
+ 4. Push to the branch (`git push origin my-new-feature`)
166
+ 5. [Create a Pull Request](hhttps://github.com/phlipper/rails-four-queueing/pull/new)
167
+
168
+
169
+ ## License
170
+
171
+ **rails-queue**
172
+
173
+ * Freely distributable and licensed under the [MIT license](http://probablywrong.mit-license.org/2012/license.html).
174
+ * Copyright (c) 2012 We're Probably Wrong (github@wereprobablywrong.com)
175
+ * http://wereprobablywrong.com/
176
+
177
+ **rails**
178
+
179
+ * Ruby on Rails is released under the [MIT License](http://www.opensource.org/licenses/MIT).
@@ -0,0 +1,12 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "lib"
7
+ t.libs << "test"
8
+ t.pattern = "test/**/*_test.rb"
9
+ t.verbose = false
10
+ end
11
+
12
+ task default: :test
@@ -0,0 +1,15 @@
1
+ require "active_support/concern"
2
+
3
+ $: << File.expand_path("../", __FILE__)
4
+
5
+ require "rails/queue/configuration"
6
+ require "rails/queue/application"
7
+ require "rails/queue/queue"
8
+ require "rails/queue/action_mailer/queued_message"
9
+
10
+ require "rails/queue/railtie"
11
+
12
+ module Rails
13
+ module Queue
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ # Original File: RAILS_ROOT/actionmailer/lib/action_mailer/queued_message.rb
2
+ require "delegate"
3
+
4
+ module Rails
5
+ module Queue
6
+ module ActionMailer
7
+ class QueuedMessage < ::Delegator
8
+ attr_reader :queue
9
+
10
+ def initialize(queue, mailer_class, method_name, *args)
11
+ @queue = queue
12
+ @job = DeliveryJob.new(mailer_class, method_name, args)
13
+ end
14
+
15
+ def __getobj__
16
+ @job.message
17
+ end
18
+
19
+ # Queues the message for delivery.
20
+ def deliver
21
+ tap { @queue.push @job }
22
+ end
23
+
24
+ class DeliveryJob
25
+ def initialize(mailer_class, method_name, args)
26
+ @mailer_class = mailer_class
27
+ @method_name = method_name
28
+ @args = args
29
+ end
30
+
31
+ def message
32
+ @message ||= @mailer_class.send(:new, @method_name, *@args).message
33
+ end
34
+
35
+ def run
36
+ message.deliver
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ module Rails
2
+ module Queue
3
+ module Application
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :queue_consumer
8
+ attr_writer :queue
9
+ end
10
+
11
+ def queue #:nodoc:
12
+ @queue ||= config.queue || Rails::Queue::Queue.new
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Rails
2
+ module Queue
3
+ module Configuration
4
+ extend ::ActiveSupport::Concern
5
+
6
+ included do
7
+ attr_accessor :queue, :queue_consumer
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,110 @@
1
+ # Original File: RAILS_ROOT/activesupport/lib/active_support/queueing.rb
2
+ require "delegate"
3
+ require "thread"
4
+
5
+ module Rails
6
+ module Queue
7
+
8
+ # A Queue that simply inherits from STDLIB's Queue. When this
9
+ # queue is used, Rails automatically starts a job runner in a
10
+ # background thread.
11
+ class Queue < ::Queue
12
+ attr_writer :consumer
13
+
14
+ def initialize(consumer_options = {})
15
+ super()
16
+ @consumer_options = consumer_options
17
+ end
18
+
19
+ def consumer
20
+ @consumer ||= ThreadedQueueConsumer.new(self, @consumer_options)
21
+ end
22
+
23
+ # Drain the queue, running all jobs in a different thread. This method
24
+ # may not be available on production queues.
25
+ def drain
26
+ # run the jobs in a separate thread so assumptions of synchronous
27
+ # jobs are caught in test mode.
28
+ consumer.drain
29
+ end
30
+ end
31
+
32
+ class SynchronousQueue < Queue
33
+ def push(job)
34
+ super.tap { drain }
35
+ end
36
+ alias << push
37
+ alias enq push
38
+ end
39
+
40
+ # In test mode, the Rails queue is backed by an Array so that assertions
41
+ # can be made about its contents. The test queue provides a +jobs+
42
+ # method to make assertions about the queue's contents and a +drain+
43
+ # method to drain the queue and run the jobs.
44
+ #
45
+ # Jobs are run in a separate thread to catch mistakes where code
46
+ # assumes that the job is run in the same thread.
47
+ class TestQueue < Queue
48
+ # Get a list of the jobs off this queue. This method may not be
49
+ # available on production queues.
50
+ def jobs
51
+ @que.dup
52
+ end
53
+
54
+ # Marshal and unmarshal job before pushing it onto the queue. This will
55
+ # raise an exception on any attempts in tests to push jobs that can't (or
56
+ # shouldn't) be marshalled.
57
+ def push(job)
58
+ super Marshal.load(Marshal.dump(job))
59
+ end
60
+ end
61
+
62
+ # The threaded consumer will run jobs in a background thread in
63
+ # development mode or in a VM where running jobs on a thread in
64
+ # production mode makes sense.
65
+ #
66
+ # When the process exits, the consumer pushes a nil onto the
67
+ # queue and joins the thread, which will ensure that all jobs
68
+ # are executed before the process finally dies.
69
+ class ThreadedQueueConsumer
70
+ attr_accessor :logger
71
+
72
+ def initialize(queue, options = {})
73
+ @queue = queue
74
+ @logger = options[:logger]
75
+ @fallback_logger = Logger.new($stderr)
76
+ end
77
+
78
+ def start
79
+ @thread = Thread.new { consume }
80
+ self
81
+ end
82
+
83
+ def shutdown
84
+ @queue.push nil
85
+ @thread.join
86
+ end
87
+
88
+ def drain
89
+ @queue.pop.run until @queue.empty?
90
+ end
91
+
92
+ def consume
93
+ while job = @queue.pop
94
+ run job
95
+ end
96
+ end
97
+
98
+ def run(job)
99
+ job.run
100
+ rescue Exception => exception
101
+ handle_exception job, exception
102
+ end
103
+
104
+ def handle_exception(job, exception)
105
+ (logger || @fallback_logger).error "Job Error: #{job.inspect}\n#{exception.message}\n#{exception.backtrace.join("\n")}"
106
+ end
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,64 @@
1
+ require "rails"
2
+
3
+ module Rails
4
+ module Queue
5
+
6
+ # Public: Initialize Rails 4 Queue
7
+ class Railtie < ::Rails::Railtie
8
+
9
+ # mixin the queue and queue_consumer to Configuration
10
+ Rails::Application::Configuration.send :include,
11
+ Rails::Queue::Configuration
12
+
13
+ # mixin the queue and queue_consumer to Application with default Queue
14
+ Rails::Application.send :include, Rails::Queue::Application
15
+
16
+ # provide a `queue` accessor from the top-level module
17
+ def Rails.queue
18
+ application.queue
19
+ end
20
+
21
+ # initialize the configuration instance queue if needed
22
+ config.after_initialize do |app|
23
+ unless app.config.instance_variable_get(:"@queue")
24
+ app.config.instance_variable_set(
25
+ :"@queue", Rails::Queue::SynchronousQueue.new
26
+ )
27
+ end
28
+ end
29
+
30
+ # initialize the queue_consumer
31
+ # copied from: railties/lib/rails/application/finisher.rb
32
+ initializer :activate_queue_consumer, after: "finisher_hook" do |app|
33
+ if app.config.queue.class == Rails::Queue::Queue
34
+ app.queue_consumer = app.config.queue_consumer || app.config.queue.consumer
35
+ app.queue_consumer.logger ||= Rails.logger if app.queue_consumer.respond_to?(:logger=)
36
+ app.queue_consumer.start
37
+ at_exit { app.queue_consumer.shutdown }
38
+ end
39
+ end
40
+
41
+ # initialize ActionMailer queue configuration
42
+ config.after_initialize do |app|
43
+ app.config.action_mailer.queue ||= app.queue
44
+ end
45
+
46
+ # enable ActionMailer queued delivery
47
+ ::ActiveSupport.on_load :action_mailer do
48
+ class_attribute :queue
49
+ self.queue = Rails::Queue::SynchronousQueue.new
50
+
51
+ # override default implementation to provide a QueuedMessage
52
+ # copied from Rails 4
53
+ def self.method_missing(method_name, *args)
54
+ if action_methods.include?(method_name.to_s)
55
+ Rails::Queue::ActionMailer::QueuedMessage.new(queue, self, method_name, *args)
56
+ else
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end