rails-queue 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +20 -0
- data/README.md +179 -0
- data/Rakefile +12 -0
- data/lib/rails-queue.rb +15 -0
- data/lib/rails/queue/action_mailer/queued_message.rb +42 -0
- data/lib/rails/queue/application.rb +17 -0
- data/lib/rails/queue/configuration.rb +12 -0
- data/lib/rails/queue/queue.rb +110 -0
- data/lib/rails/queue/railtie.rb +64 -0
- data/lib/rails/queue/version.rb +5 -0
- data/test/ci/before_script.sh +4 -0
- data/test/fixtures/async_mailer/welcome.erb +1 -0
- data/test/rails-queue_test.rb +7 -0
- data/test/rails/frameworks_test.rb +35 -0
- data/test/rails/queue/actionmailer/base_test.rb +22 -0
- data/test/rails/queue/synchronous_queue_test.rb +28 -0
- data/test/rails/queue/test_queue_test.rb +147 -0
- data/test/rails/queue/threaded_consumer_test.rb +111 -0
- data/test/rails/queue_test.rb +152 -0
- data/test/support/action_mailer_abstract_unit.rb +65 -0
- data/test/support/active_support_abstract_unit.rb +26 -0
- data/test/support/empty_bool.rb +8 -0
- data/test/support/isolation_abstract_unit.rb +305 -0
- data/test/support/mailers.rb +18 -0
- metadata +107 -0
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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).
|
data/Rakefile
ADDED
data/lib/rails-queue.rb
ADDED
@@ -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,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
|