rails-four-queueing 0.1.0

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