rails-four-queueing 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
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,97 @@
1
+ # Rails::Four::Queueing
2
+
3
+ [![Build Status](https://secure.travis-ci.org/phlipper/rails-four-queueing.png)](https://next.travis-ci.org/phlipper/rails-four-queueing)
4
+
5
+ ## Description
6
+
7
+ It's the Queue from Rails 4, backported to Rails 3.2+ for your Queueing pleasure.
8
+
9
+
10
+ ## Requirements
11
+
12
+ This gem requires Rails 3.2+ and has been tested on the following versions:
13
+
14
+ * 3.2.8
15
+
16
+ This gem has been tested against the following Ruby versions:
17
+
18
+ * 1.9.2
19
+ * 1.9.3
20
+
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ ```ruby
27
+ gem "rails-four-queueing"
28
+ ```
29
+
30
+ And then execute:
31
+
32
+ ```
33
+ $ bundle
34
+ ```
35
+
36
+ Or install it yourself as:
37
+
38
+ ```
39
+ $ gem install rails-four-queueing
40
+ ```
41
+
42
+
43
+ ## Usage
44
+
45
+ It's the [Rails 4 Queue](https://github.com/rails/rails/blob/master/activesupport/lib/active_support/queueing.rb):
46
+
47
+ ```
48
+ Rails.queue is the application's queue. You can push a job onto
49
+ the queue by:
50
+
51
+ Rails.queue.push job
52
+
53
+ A job is an object that responds to +run+. Queue consumers will
54
+ pop jobs off of the queue and invoke the queue's +run+ method.
55
+
56
+ Note that depending on your queue implementation, jobs may not
57
+ be executed in the same process as they were created in, and
58
+ are never executed in the same thread as they were created in.
59
+
60
+ If necessary, a queue implementation may need to serialize your
61
+ job for distribution to another process. The documentation of
62
+ your queue will specify the requirements for that serialization
63
+ ```
64
+
65
+
66
+ ## Contributing
67
+
68
+ 1. [Fork it](https://github.com/phlipper/rails-four-queueing/fork_select)
69
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
70
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
71
+ 4. Push to the branch (`git push origin my-new-feature`)
72
+ 5. [Create a Pull Request](hhttps://github.com/phlipper/rails-four-queueing/pull/new)
73
+
74
+
75
+ ## Contributors
76
+
77
+ Many thanks go to the following who have contributed to making this gem even better:
78
+
79
+ * **[@rails](https://github.com/rails)**
80
+ * for making this all possible in the first place
81
+ * **[@elskwid](https://github.com/elskwid)**
82
+ * for providing the inspiration, and the persperation, to see this through to the bitter end
83
+ * **[@phlipper](https://github.com/phlipper)**
84
+ * for providing the inspiration, and the persperation, to see this through to the bitter end
85
+
86
+
87
+ ## License
88
+
89
+ **rails-four-queueing**
90
+
91
+ * Freely distributable and licensed under the [MIT license](http://phlipper.mit-license.org/2012/license.html).
92
+ * Copyright (c) 2012 Phil Cohen (github@phlippers.net) [![endorse](http://api.coderwall.com/phlipper/endorsecount.png)](http://coderwall.com/phlipper)
93
+ * http://phlippers.net/
94
+
95
+ **rails**
96
+
97
+ * Ruby on Rails is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Rails::Four::Queueing'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,8 @@
1
+ require "rails/four/queueing/engine"
2
+
3
+ module Rails
4
+ module Four
5
+ module Queueing
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ module Rails
2
+ module Four
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::Four::Queueing::Queue.new
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,12 @@
1
+ module Rails
2
+ module Four
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,106 @@
1
+ # Original File: RAILS_ROOT/activesupport/lib/active_support/queueing.rb
2
+ require "delegate"
3
+ require "thread"
4
+
5
+ module Rails::Four::Queueing
6
+ # A Queue that simply inherits from STDLIB's Queue. When this
7
+ # queue is used, Rails automatically starts a job runner in a
8
+ # background thread.
9
+ class Queue < ::Queue
10
+ attr_writer :consumer
11
+
12
+ def initialize(consumer_options = {})
13
+ super()
14
+ @consumer_options = consumer_options
15
+ end
16
+
17
+ def consumer
18
+ @consumer ||= ThreadedQueueConsumer.new(self, @consumer_options)
19
+ end
20
+
21
+ # Drain the queue, running all jobs in a different thread. This method
22
+ # may not be available on production queues.
23
+ def drain
24
+ # run the jobs in a separate thread so assumptions of synchronous
25
+ # jobs are caught in test mode.
26
+ consumer.drain
27
+ end
28
+ end
29
+
30
+ class SynchronousQueue < Queue
31
+ def push(job)
32
+ super.tap { drain }
33
+ end
34
+ alias << push
35
+ alias enq push
36
+ end
37
+
38
+ # In test mode, the Rails queue is backed by an Array so that assertions
39
+ # can be made about its contents. The test queue provides a +jobs+
40
+ # method to make assertions about the queue's contents and a +drain+
41
+ # method to drain the queue and run the jobs.
42
+ #
43
+ # Jobs are run in a separate thread to catch mistakes where code
44
+ # assumes that the job is run in the same thread.
45
+ class TestQueue < Queue
46
+ # Get a list of the jobs off this queue. This method may not be
47
+ # available on production queues.
48
+ def jobs
49
+ @que.dup
50
+ end
51
+
52
+ # Marshal and unmarshal job before pushing it onto the queue. This will
53
+ # raise an exception on any attempts in tests to push jobs that can't (or
54
+ # shouldn't) be marshalled.
55
+ def push(job)
56
+ super Marshal.load(Marshal.dump(job))
57
+ end
58
+ end
59
+
60
+ # The threaded consumer will run jobs in a background thread in
61
+ # development mode or in a VM where running jobs on a thread in
62
+ # production mode makes sense.
63
+ #
64
+ # When the process exits, the consumer pushes a nil onto the
65
+ # queue and joins the thread, which will ensure that all jobs
66
+ # are executed before the process finally dies.
67
+ class ThreadedQueueConsumer
68
+ attr_accessor :logger
69
+
70
+ def initialize(queue, options = {})
71
+ @queue = queue
72
+ @logger = options[:logger]
73
+ @fallback_logger = Logger.new($stderr)
74
+ end
75
+
76
+ def start
77
+ @thread = Thread.new { consume }
78
+ self
79
+ end
80
+
81
+ def shutdown
82
+ @queue.push nil
83
+ @thread.join
84
+ end
85
+
86
+ def drain
87
+ @queue.pop.run until @queue.empty?
88
+ end
89
+
90
+ def consume
91
+ while job = @queue.pop
92
+ run job
93
+ end
94
+ end
95
+
96
+ def run(job)
97
+ job.run
98
+ rescue Exception => exception
99
+ handle_exception job, exception
100
+ end
101
+
102
+ def handle_exception(job, exception)
103
+ (logger || @fallback_logger).error "Job Error: #{job.inspect}\n#{exception.message}\n#{exception.backtrace.join("\n")}"
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,50 @@
1
+ require "rails"
2
+ require "rails/four/configuration"
3
+ require "rails/four/application"
4
+
5
+ module Rails
6
+ module Four
7
+ module Queueing
8
+
9
+ # Public: Initialize Rails 4 Queue
10
+ class Engine < ::Rails::Engine
11
+
12
+ # mixin the queue and queue_consumer to Configuration
13
+ Rails::Application::Configuration.send :include,
14
+ Rails::Four::Configuration
15
+
16
+ # mixin the queue and queue_consumer to Application with default Queue
17
+ Rails::Application.send :include, Rails::Four::Application
18
+
19
+ # provide a `queue` accessor from the top-level module
20
+ def Rails.queue
21
+ application.queue
22
+ end
23
+
24
+ # initialize the configuration instance queue if needed
25
+ config.after_initialize do |app|
26
+ unless app.config.instance_variable_get(:"@queue")
27
+ app.config.instance_variable_set(
28
+ :"@queue", Rails::Four::Queueing::SynchronousQueue.new
29
+ )
30
+ end
31
+ end
32
+
33
+ # initialize the queue_consumer
34
+ # copied from: railties/lib/rails/application/finisher.rb
35
+ initializer :activate_queue_consumer, after: "finisher_hook" do |app|
36
+ if app.config.queue.class == Rails::Four::Queueing::Queue
37
+ app.queue_consumer = app.config.queue_consumer ||
38
+ app.config.queue.consumer
39
+ if app.queue_consumer.respond_to?(:logger=)
40
+ app.queue_consumer.logger ||= Rails.logger
41
+ end
42
+ app.queue_consumer.start
43
+ at_exit { app.queue_consumer.shutdown }
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,7 @@
1
+ module Rails
2
+ module Four
3
+ module Queueing
4
+ VERSION = "0.1.0"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rails-four-queueing do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,4 @@
1
+ # clone Rails 3.2 one directory up from our library
2
+ git clone git://github.com/rails/rails.git ../rails
3
+ cd ../rails
4
+ git checkout v3.2.8
@@ -0,0 +1,7 @@
1
+ require File.expand_path("../../lib/rails-four-queueing", __FILE__)
2
+
3
+ class Rails::Four::QueueingTest < ActiveSupport::TestCase
4
+ test "module definition" do
5
+ assert_kind_of Module, Rails::Four::Queueing
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ # Original File: RAILS_ROOT/activesupport/test/queueing/synchronous_queue_test.rb
2
+ require "support/active_support_abstract_unit"
3
+ require "rails/four/queueing"
4
+
5
+ class SynchronousQueueTest < ActiveSupport::TestCase
6
+ class Job
7
+ attr_reader :ran
8
+ def run; @ran = true end
9
+ end
10
+
11
+ class ExceptionRaisingJob
12
+ def run; raise end
13
+ end
14
+
15
+ def setup
16
+ @queue = Rails::Four::Queueing::SynchronousQueue.new
17
+ end
18
+
19
+ def test_runs_jobs_immediately
20
+ job = Job.new
21
+ @queue.push job
22
+ assert job.ran
23
+
24
+ assert_raises RuntimeError do
25
+ @queue.push ExceptionRaisingJob.new
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,147 @@
1
+ # Original File: RAILS_ROOT/activesupport/test/queueing/test_queue_test.rb
2
+ require "support/active_support_abstract_unit"
3
+ require "rails/four/queueing"
4
+
5
+ class TestQueueTest < ActiveSupport::TestCase
6
+ def setup
7
+ @queue = Rails::Four::Queueing::TestQueue.new
8
+ end
9
+
10
+ class ExceptionRaisingJob
11
+ def run
12
+ raise
13
+ end
14
+ end
15
+
16
+ def test_drain_raises_exceptions_from_running_jobs
17
+ @queue.push ExceptionRaisingJob.new
18
+ assert_raises(RuntimeError) { @queue.drain }
19
+ end
20
+
21
+ def test_jobs
22
+ @queue.push 1
23
+ @queue.push 2
24
+ assert_equal [1,2], @queue.jobs
25
+ end
26
+
27
+ class EquivalentJob
28
+ def initialize
29
+ @initial_id = self.object_id
30
+ end
31
+
32
+ def run
33
+ end
34
+
35
+ def ==(other)
36
+ other.same_initial_id?(@initial_id)
37
+ end
38
+
39
+ def same_initial_id?(other_id)
40
+ other_id == @initial_id
41
+ end
42
+ end
43
+
44
+ def test_contents
45
+ job = EquivalentJob.new
46
+ assert @queue.empty?
47
+ @queue.push job
48
+ refute @queue.empty?
49
+ assert_equal job, @queue.pop
50
+ end
51
+
52
+ class ProcessingJob
53
+ def self.clear_processed
54
+ @processed = []
55
+ end
56
+
57
+ def self.processed
58
+ @processed
59
+ end
60
+
61
+ def initialize(object)
62
+ @object = object
63
+ end
64
+
65
+ def run
66
+ self.class.processed << @object
67
+ end
68
+ end
69
+
70
+ def test_order
71
+ ProcessingJob.clear_processed
72
+ job1 = ProcessingJob.new(1)
73
+ job2 = ProcessingJob.new(2)
74
+
75
+ @queue.push job1
76
+ @queue.push job2
77
+ @queue.drain
78
+
79
+ assert_equal [1,2], ProcessingJob.processed
80
+ end
81
+
82
+ class ThreadTrackingJob
83
+ attr_reader :thread_id
84
+
85
+ def run
86
+ @thread_id = Thread.current.object_id
87
+ end
88
+
89
+ def ran?
90
+ @thread_id
91
+ end
92
+ end
93
+
94
+ def test_drain
95
+ @queue.push ThreadTrackingJob.new
96
+ job = @queue.jobs.last
97
+ @queue.drain
98
+
99
+ assert @queue.empty?
100
+ assert job.ran?, "The job runs synchronously when the queue is drained"
101
+ assert_equal job.thread_id, Thread.current.object_id
102
+ end
103
+
104
+ class IdentifiableJob
105
+ def initialize(id)
106
+ @id = id
107
+ end
108
+
109
+ def ==(other)
110
+ other.same_id?(@id)
111
+ end
112
+
113
+ def same_id?(other_id)
114
+ other_id == @id
115
+ end
116
+
117
+ def run
118
+ end
119
+ end
120
+
121
+ def test_queue_can_be_observed
122
+ jobs = (1..10).map do |id|
123
+ IdentifiableJob.new(id)
124
+ end
125
+
126
+ jobs.each do |job|
127
+ @queue.push job
128
+ end
129
+
130
+ assert_equal jobs, @queue.jobs
131
+ end
132
+
133
+ def test_adding_an_unmarshallable_job
134
+ anonymous_class_instance = Struct.new(:run).new
135
+
136
+ assert_raises TypeError do
137
+ @queue.push anonymous_class_instance
138
+ end
139
+ end
140
+
141
+ def test_attempting_to_add_a_reference_to_itself
142
+ job = {reference: @queue}
143
+ assert_raises TypeError do
144
+ @queue.push job
145
+ end
146
+ end
147
+ end