boss_queue 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/.rspec +1 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +92 -0
- data/LICENSE +20 -0
- data/README.md +81 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/boss_queue/boss_queue.rb +104 -0
- data/lib/boss_queue/job.rb +100 -0
- data/lib/boss_queue.rb +2 -0
- data/spec/boss_queue_spec.rb +244 -0
- data/spec/job_spec.rb +358 -0
- data/spec/spec_helper.rb +31 -0
- metadata +175 -0
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gem 'aws-sdk'
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'rspec'
|
7
|
+
gem 'bundler'
|
8
|
+
gem 'jeweler', :git => 'https://github.com/technicalpickles/jeweler.git', :branch => :master
|
9
|
+
gem 'pry'
|
10
|
+
gem 'pry-nav'
|
11
|
+
gem 'pry-stack_explorer'
|
12
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
GIT
|
2
|
+
remote: https://github.com/technicalpickles/jeweler.git
|
3
|
+
revision: f7e0a55a207d83f56637dd8fbabf26a803410faf
|
4
|
+
branch: master
|
5
|
+
specs:
|
6
|
+
jeweler (1.8.7)
|
7
|
+
builder
|
8
|
+
bundler (~> 1.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
github_api (= 0.10.1)
|
11
|
+
highline (>= 1.6.15)
|
12
|
+
nokogiri (= 1.5.10)
|
13
|
+
rake
|
14
|
+
rdoc
|
15
|
+
|
16
|
+
GEM
|
17
|
+
remote: https://rubygems.org/
|
18
|
+
specs:
|
19
|
+
addressable (2.3.5)
|
20
|
+
aws-sdk (1.19.0)
|
21
|
+
json (~> 1.4)
|
22
|
+
nokogiri (>= 1.4.4, < 1.6.0)
|
23
|
+
uuidtools (~> 2.1)
|
24
|
+
binding_of_caller (0.7.2)
|
25
|
+
debug_inspector (>= 0.0.1)
|
26
|
+
builder (3.2.2)
|
27
|
+
coderay (1.0.9)
|
28
|
+
debug_inspector (0.0.2)
|
29
|
+
diff-lcs (1.2.4)
|
30
|
+
faraday (0.8.8)
|
31
|
+
multipart-post (~> 1.2.0)
|
32
|
+
git (1.2.6)
|
33
|
+
github_api (0.10.1)
|
34
|
+
addressable
|
35
|
+
faraday (~> 0.8.1)
|
36
|
+
hashie (>= 1.2)
|
37
|
+
multi_json (~> 1.4)
|
38
|
+
nokogiri (~> 1.5.2)
|
39
|
+
oauth2
|
40
|
+
hashie (2.0.5)
|
41
|
+
highline (1.6.19)
|
42
|
+
httpauth (0.2.0)
|
43
|
+
json (1.8.0)
|
44
|
+
jwt (0.1.8)
|
45
|
+
multi_json (>= 1.5)
|
46
|
+
method_source (0.8.2)
|
47
|
+
multi_json (1.8.0)
|
48
|
+
multi_xml (0.5.5)
|
49
|
+
multipart-post (1.2.0)
|
50
|
+
nokogiri (1.5.10)
|
51
|
+
oauth2 (0.9.2)
|
52
|
+
faraday (~> 0.8)
|
53
|
+
httpauth (~> 0.2)
|
54
|
+
jwt (~> 0.1.4)
|
55
|
+
multi_json (~> 1.0)
|
56
|
+
multi_xml (~> 0.5)
|
57
|
+
rack (~> 1.2)
|
58
|
+
pry (0.9.12.2)
|
59
|
+
coderay (~> 1.0.5)
|
60
|
+
method_source (~> 0.8)
|
61
|
+
slop (~> 3.4)
|
62
|
+
pry-nav (0.2.3)
|
63
|
+
pry (~> 0.9.10)
|
64
|
+
pry-stack_explorer (0.4.9.1)
|
65
|
+
binding_of_caller (>= 0.7)
|
66
|
+
pry (>= 0.9.11)
|
67
|
+
rack (1.5.2)
|
68
|
+
rake (10.1.0)
|
69
|
+
rdoc (4.0.1)
|
70
|
+
json (~> 1.4)
|
71
|
+
rspec (2.14.1)
|
72
|
+
rspec-core (~> 2.14.0)
|
73
|
+
rspec-expectations (~> 2.14.0)
|
74
|
+
rspec-mocks (~> 2.14.0)
|
75
|
+
rspec-core (2.14.5)
|
76
|
+
rspec-expectations (2.14.3)
|
77
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
78
|
+
rspec-mocks (2.14.3)
|
79
|
+
slop (3.4.6)
|
80
|
+
uuidtools (2.1.4)
|
81
|
+
|
82
|
+
PLATFORMS
|
83
|
+
ruby
|
84
|
+
|
85
|
+
DEPENDENCIES
|
86
|
+
aws-sdk
|
87
|
+
bundler
|
88
|
+
jeweler!
|
89
|
+
pry
|
90
|
+
pry-nav
|
91
|
+
pry-stack_explorer
|
92
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Populr.me
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
boss_queue
|
2
|
+
==========
|
3
|
+
|
4
|
+
A fault tolerant job queue built around Amazon SQS & DynamoDB
|
5
|
+
|
6
|
+
|
7
|
+
Setup
|
8
|
+
============
|
9
|
+
|
10
|
+
In your Gemfile:
|
11
|
+
|
12
|
+
gem 'boss_queue'
|
13
|
+
|
14
|
+
|
15
|
+
boss_queue uses an Amazon SQS queue and a Amazon DynamoDB table for each environment (production, staging, test). To set these up in Rails do:
|
16
|
+
|
17
|
+
$ rails c
|
18
|
+
|
19
|
+
AWS.config(:access_key_id => <access_key_id>,
|
20
|
+
:secret_access_key => <secret_access_key>)
|
21
|
+
|
22
|
+
BossQueue.environment = 'development'
|
23
|
+
BossQueue.create_table
|
24
|
+
BossQueue.create_queue
|
25
|
+
|
26
|
+
BossQueue.environment = 'staging'
|
27
|
+
BossQueue.create_table
|
28
|
+
BossQueue.create_queue
|
29
|
+
|
30
|
+
# BossQueue.create_table(read_capacity, write_capacity)
|
31
|
+
# One read capacity unit = two eventually consistent reads per second, for items up 4 KB in size.
|
32
|
+
# One write capacity unit = one write per second, for items up to 1 KB in size.
|
33
|
+
|
34
|
+
BossQueue.environment = 'production'
|
35
|
+
BossQueue.create_table(50, 10)
|
36
|
+
BossQueue.create_queue
|
37
|
+
|
38
|
+
|
39
|
+
Alternatively, in each of the respective environments, do:
|
40
|
+
|
41
|
+
$ rails c
|
42
|
+
|
43
|
+
AWS.config(:access_key_id => <access_key_id>,
|
44
|
+
:secret_access_key => <secret_access_key>)
|
45
|
+
|
46
|
+
# environment does not need to be set because it is taken from Rails.env
|
47
|
+
BossQueue.create_table
|
48
|
+
BossQueue.create_queue
|
49
|
+
|
50
|
+
|
51
|
+
Or these could be put into a migration.
|
52
|
+
|
53
|
+
|
54
|
+
Usage
|
55
|
+
=====
|
56
|
+
|
57
|
+
myobject = MyClass.new
|
58
|
+
BossQueue.failure_action = 'none' # default is 'retry' which retries up to four times
|
59
|
+
|
60
|
+
# can enqueue instance methods (assumes that objects have an id and a #find(id) method)
|
61
|
+
BossQueue.enqueue(myobject, :method_to_execute, arg1, arg2)
|
62
|
+
# enqueue with a delay of up to 900 seconds (15 minutes)
|
63
|
+
BossQueue.enqueue_with_delay(60, myobject, :method_to_execute, arg1, arg2, arg3)
|
64
|
+
|
65
|
+
# can enqueue class methods
|
66
|
+
BossQueue.enqueue(MyClass, :method_to_execute)
|
67
|
+
BossQueue.enqueue_with_delay(60, MyClass, :method_to_execute, arg1, arg2)
|
68
|
+
|
69
|
+
BossQueue.work
|
70
|
+
|
71
|
+
# failures are left in the DynamoDB table with the failed boolean set to true
|
72
|
+
|
73
|
+
BossQueue does not at present have a daemon component such as Sidekiq or Resque.
|
74
|
+
|
75
|
+
|
76
|
+
Future Work
|
77
|
+
===========
|
78
|
+
|
79
|
+
Create some mechanism for viewing failed jobs (and perhaps queued jobs...they are all in the same table)
|
80
|
+
|
81
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "boss_queue"
|
18
|
+
gem.homepage = "https://github.com/populr/boss_queue"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{A fault tolerant job queue built around Amazon SQS & DynamoDB}
|
21
|
+
gem.description = %Q{A fault tolerant job queue built around Amazon SQS & DynamoDB}
|
22
|
+
gem.email = "daniel@populr.me"
|
23
|
+
gem.authors = ["Daniel Nelson"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rdoc/task'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "boss_queue #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class BossQueue
|
4
|
+
@@environment
|
5
|
+
|
6
|
+
def self.environment=(env)
|
7
|
+
@@environment = env
|
8
|
+
end
|
9
|
+
|
10
|
+
@@failure_action
|
11
|
+
|
12
|
+
def self.failure_action
|
13
|
+
@@failure_action ||= 'retry'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.failure_action=(env)
|
17
|
+
@@failure_action = env
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def self.table_name
|
22
|
+
"#{self.queue_prefix}boss_queue_jobs"
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.queue_name
|
26
|
+
"#{self.queue_prefix}boss_queue"
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def self.create_table(read_capacity=1, write_capacity=1, options={})
|
31
|
+
create_opts = {}
|
32
|
+
create_opts[:hash_key] = { hash_key => :string }
|
33
|
+
create_opts[:range_key] = { :kind => :string }
|
34
|
+
|
35
|
+
AWS::DynamoDB.new.tables.create(self.table_name, read_capacity, write_capacity, create_opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.create_queue
|
39
|
+
AWS::SQS::QueueCollection.new.create(self.queue_name, :default_visibility_timeout => 5 * 60)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.work
|
43
|
+
queue = AWS::SQS.new.queues[self.queue_name]
|
44
|
+
queue.receive_message do |job_id|
|
45
|
+
# When a block is given, each message is yielded to the block and then deleted as long as the block exits normally - http://docs.aws.amazon.com/AWSRubySDK/latest/frames.html
|
46
|
+
job = BossQueue::Job.shard(table_name).find_by_id(job_id.body)
|
47
|
+
job.queue_name = self.queue_name
|
48
|
+
job.work
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.enqueue(class_or_instance, method_name, *args)
|
53
|
+
job = self.create_job(class_or_instance, method_name, *args)
|
54
|
+
job.enqueue
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.enqueue_with_delay(delay, class_or_instance, method_name, *args)
|
58
|
+
job = self.create_job(class_or_instance, method_name, *args)
|
59
|
+
job.enqueue_with_delay(delay)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.create_job(class_or_instance, method_name, *args) # :nodoc:
|
63
|
+
job = BossQueue::Job.shard(table_name).new
|
64
|
+
if class_or_instance.is_a?(Class)
|
65
|
+
class_name = class_or_instance.to_s
|
66
|
+
instance_id = nil
|
67
|
+
job.kind = "#{class_name}@#{method_name}"
|
68
|
+
else
|
69
|
+
class_name = class_or_instance.class.to_s
|
70
|
+
instance_id = class_or_instance.id
|
71
|
+
job.kind = "#{class_name}##{method_name}"
|
72
|
+
end
|
73
|
+
job.queue_name = self.queue_name
|
74
|
+
job.failure_action = self.failure_action
|
75
|
+
job.model_class_name = class_name
|
76
|
+
job.model_id = instance_id unless instance_id.nil?
|
77
|
+
job.job_method = method_name.to_s
|
78
|
+
job.job_arguments = JSON.generate(args)
|
79
|
+
job.save!
|
80
|
+
job
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.environment # :nodoc:
|
84
|
+
@@environment ||= if Module.const_get('Rails')
|
85
|
+
Rails.env
|
86
|
+
elsif Module.const_get('Rack')
|
87
|
+
Rack.env
|
88
|
+
else
|
89
|
+
raise 'BossQueue requires an environment'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.queue_prefix # :nodoc:
|
94
|
+
case self.environment
|
95
|
+
when 'production'
|
96
|
+
''
|
97
|
+
when 'development'
|
98
|
+
'dev_'
|
99
|
+
else
|
100
|
+
environment + '_'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class BossQueue
|
4
|
+
|
5
|
+
class Job < AWS::Record::HashModel
|
6
|
+
attr_accessor :queue_name
|
7
|
+
|
8
|
+
string_attr :kind # an index based model_class_name, job_method
|
9
|
+
boolean_attr :failed
|
10
|
+
|
11
|
+
string_attr :model_class_name
|
12
|
+
string_attr :model_id
|
13
|
+
string_attr :job_method
|
14
|
+
string_attr :job_arguments
|
15
|
+
|
16
|
+
integer_attr :failed_attempts
|
17
|
+
string_attr :failure_action
|
18
|
+
string_attr :exception_name
|
19
|
+
string_attr :exception_message
|
20
|
+
string_attr :stacktrace
|
21
|
+
|
22
|
+
timestamps
|
23
|
+
|
24
|
+
def enqueue
|
25
|
+
queue = AWS::SQS.new.queues[queue_name]
|
26
|
+
queue.send_message(id.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def enqueue_with_delay(delay)
|
30
|
+
queue = AWS::SQS.new.queues[queue_name]
|
31
|
+
queue.send_message(id.to_s, :delay_seconds => [900, [0, delay].max].min)
|
32
|
+
end
|
33
|
+
|
34
|
+
def work
|
35
|
+
begin
|
36
|
+
klass = constantize(model_class_name)
|
37
|
+
if model_id
|
38
|
+
target = klass.find(model_id)
|
39
|
+
else
|
40
|
+
target = klass
|
41
|
+
end
|
42
|
+
args = JSON.parse(job_arguments)
|
43
|
+
target.send(job_method, *args)
|
44
|
+
destroy
|
45
|
+
rescue StandardError => err
|
46
|
+
fail(err)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def fail(err)
|
51
|
+
self.failed_attempts ||= 0
|
52
|
+
self.failed_attempts += 1
|
53
|
+
self.exception_name = err.class.to_s
|
54
|
+
self.exception_message = err.message
|
55
|
+
self.stacktrace = err.backtrace[0, 7].join("\n")
|
56
|
+
|
57
|
+
if failure_action == 'retry' && retry_delay
|
58
|
+
enqueue_with_delay(retry_delay)
|
59
|
+
else
|
60
|
+
self.failed = true
|
61
|
+
end
|
62
|
+
|
63
|
+
self.save!
|
64
|
+
end
|
65
|
+
|
66
|
+
def retry_delay
|
67
|
+
return nil if failed_attempts.nil? || failed_attempts > 4
|
68
|
+
60 * 2**(failed_attempts - 1)
|
69
|
+
end
|
70
|
+
|
71
|
+
# from ActiveSupport source: http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize
|
72
|
+
def constantize(camel_cased_word) # :nodoc:
|
73
|
+
names = camel_cased_word.split('::')
|
74
|
+
names.shift if names.empty? || names.first.empty?
|
75
|
+
|
76
|
+
names.inject(Object) do |constant, name|
|
77
|
+
if constant == Object
|
78
|
+
constant.const_get(name)
|
79
|
+
else
|
80
|
+
candidate = constant.const_get(name)
|
81
|
+
next candidate if constant.const_defined?(name, false)
|
82
|
+
next candidate unless Object.const_defined?(name)
|
83
|
+
|
84
|
+
# Go down the ancestors to check it it's owned
|
85
|
+
# directly before we reach Object or the end of ancestors.
|
86
|
+
constant = constant.ancestors.inject do |const, ancestor|
|
87
|
+
break const if ancestor == Object
|
88
|
+
break ancestor if ancestor.const_defined?(name, false)
|
89
|
+
const
|
90
|
+
end
|
91
|
+
|
92
|
+
# owner is in Object, so raise
|
93
|
+
constant.const_get(name, false)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
data/lib/boss_queue.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "BossQueue module" do
|
4
|
+
|
5
|
+
it "should respond to environment" do
|
6
|
+
BossQueue.should respond_to(:environment)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should respond to environment=" do
|
10
|
+
BossQueue.should respond_to(:environment=)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should respond to failure_action" do
|
14
|
+
BossQueue.should respond_to(:failure_action)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should respond to failure_action=" do
|
18
|
+
BossQueue.should respond_to(:failure_action=)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#failure_action" do
|
22
|
+
it "should default to 'retry'" do
|
23
|
+
BossQueue.failure_action.should == 'retry'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#table_name" do
|
28
|
+
before(:each) do
|
29
|
+
BossQueue.environment = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when @@environment is 'development'" do
|
33
|
+
it "should be 'dev_boss_queue_jobs'" do
|
34
|
+
BossQueue.environment = 'development'
|
35
|
+
BossQueue.table_name.should == 'dev_boss_queue_jobs'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "when @@environment is 'production'" do
|
40
|
+
it "should be 'boss_queue_jobs'" do
|
41
|
+
BossQueue.environment = 'production'
|
42
|
+
BossQueue.table_name.should == 'boss_queue_jobs'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "when @@environment is 'staging'" do
|
47
|
+
it "should be 'staging_boss_queue_jobs'" do
|
48
|
+
BossQueue.environment = 'staging'
|
49
|
+
BossQueue.table_name.should == 'staging_boss_queue_jobs'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when @@environment is 'staging'" do
|
54
|
+
it "should be 'staging_boss_queue_jobs'" do
|
55
|
+
BossQueue.environment = 'staging'
|
56
|
+
BossQueue.table_name.should == 'staging_boss_queue_jobs'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context "when @@environment is nil" do
|
61
|
+
it "should raise an exception" do
|
62
|
+
lambda {
|
63
|
+
BossQueue.table_name
|
64
|
+
}.should raise_error
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
describe "#queue_name" do
|
71
|
+
before(:each) do
|
72
|
+
BossQueue.environment = nil
|
73
|
+
end
|
74
|
+
|
75
|
+
context "when @@environment is 'development'" do
|
76
|
+
it "should be 'dev_boss_queue'" do
|
77
|
+
BossQueue.environment = 'development'
|
78
|
+
BossQueue.queue_name.should == 'dev_boss_queue'
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "when @@environment is 'production'" do
|
83
|
+
it "should be 'boss_queue'" do
|
84
|
+
BossQueue.environment = 'production'
|
85
|
+
BossQueue.queue_name.should == 'boss_queue'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when @@environment is 'staging'" do
|
90
|
+
it "should be 'staging_boss_queue'" do
|
91
|
+
BossQueue.environment = 'staging'
|
92
|
+
BossQueue.queue_name.should == 'staging_boss_queue'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context "when @@environment is 'staging'" do
|
97
|
+
it "should be 'staging_boss_queue'" do
|
98
|
+
BossQueue.environment = 'staging'
|
99
|
+
BossQueue.queue_name.should == 'staging_boss_queue'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context "when @@environment is nil" do
|
104
|
+
it "should raise an exception" do
|
105
|
+
lambda {
|
106
|
+
BossQueue.queue_name
|
107
|
+
}.should raise_error
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#enqueue" do
|
113
|
+
before(:each) do
|
114
|
+
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
115
|
+
@argument_json = JSON.generate(@arguments)
|
116
|
+
|
117
|
+
class TestClass
|
118
|
+
def id
|
119
|
+
'xyz'
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.test_class_method
|
123
|
+
end
|
124
|
+
|
125
|
+
def test_instance_method
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context "when a class" do
|
131
|
+
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
132
|
+
BossQueue.environment = 'test'
|
133
|
+
BossQueue.failure_action = 'retry'
|
134
|
+
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass@test_class_method')
|
135
|
+
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
136
|
+
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
137
|
+
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
138
|
+
BossQueue::Job.any_instance.should_not_receive(:model_id=)
|
139
|
+
BossQueue::Job.any_instance.should_receive(:job_method=).with('test_class_method')
|
140
|
+
BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
|
141
|
+
BossQueue::Job.any_instance.should_receive(:save!)
|
142
|
+
BossQueue::Job.any_instance.should_receive(:enqueue)
|
143
|
+
BossQueue.enqueue(TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context "when a class instance" do
|
148
|
+
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
149
|
+
BossQueue.environment = 'test'
|
150
|
+
BossQueue.failure_action = 'retry'
|
151
|
+
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass#test_instance_method')
|
152
|
+
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
153
|
+
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
154
|
+
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
155
|
+
BossQueue::Job.any_instance.should_receive(:model_id=).with('xyz')
|
156
|
+
BossQueue::Job.any_instance.should_receive(:job_method=).with('test_instance_method')
|
157
|
+
BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
|
158
|
+
BossQueue::Job.any_instance.should_receive(:save!)
|
159
|
+
BossQueue::Job.any_instance.should_receive(:enqueue)
|
160
|
+
BossQueue.enqueue(TestClass.new, :test_instance_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "#enqueue_with_delay" do
|
167
|
+
before(:each) do
|
168
|
+
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
169
|
+
@argument_json = JSON.generate(@arguments)
|
170
|
+
end
|
171
|
+
|
172
|
+
context "when a class" do
|
173
|
+
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
174
|
+
BossQueue.environment = 'test'
|
175
|
+
BossQueue.failure_action = 'retry'
|
176
|
+
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass@test_class_method')
|
177
|
+
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
178
|
+
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
179
|
+
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
180
|
+
BossQueue::Job.any_instance.should_not_receive(:model_id=)
|
181
|
+
BossQueue::Job.any_instance.should_receive(:job_method=).with('test_class_method')
|
182
|
+
BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
|
183
|
+
BossQueue::Job.any_instance.should_receive(:save!)
|
184
|
+
BossQueue::Job.any_instance.should_receive(:enqueue_with_delay).with(60)
|
185
|
+
BossQueue.enqueue_with_delay(60, TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when a class instance" do
|
190
|
+
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
191
|
+
BossQueue.environment = 'test'
|
192
|
+
BossQueue.failure_action = 'retry'
|
193
|
+
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass#test_instance_method')
|
194
|
+
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
195
|
+
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
196
|
+
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
197
|
+
BossQueue::Job.any_instance.should_receive(:model_id=).with('xyz')
|
198
|
+
BossQueue::Job.any_instance.should_receive(:job_method=).with('test_instance_method')
|
199
|
+
BossQueue::Job.any_instance.should_receive(:job_arguments=).with(@argument_json)
|
200
|
+
BossQueue::Job.any_instance.should_receive(:save!)
|
201
|
+
BossQueue::Job.any_instance.should_receive(:enqueue_with_delay).with(60)
|
202
|
+
BossQueue.enqueue_with_delay(60, TestClass.new, :test_instance_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
describe "#work" do
|
209
|
+
before(:each) do
|
210
|
+
@queue = double('queue')
|
211
|
+
AWS::SQS.stub_chain(:new, :queues, :[]).and_return(@queue)
|
212
|
+
|
213
|
+
@sqs_message = double('message')
|
214
|
+
@sqs_message.stub(:body).and_return('ijk')
|
215
|
+
@queue.stub(:receive_message).and_yield(@sqs_message)
|
216
|
+
|
217
|
+
@job = double('job')
|
218
|
+
@job.stub(:work)
|
219
|
+
@job.stub(:queue_name=)
|
220
|
+
BossQueue::Job.stub_chain(:shard, :find_by_id).and_return(@job)
|
221
|
+
end
|
222
|
+
|
223
|
+
it "should dequeue from SQS" do
|
224
|
+
@queue.should_receive(:receive_message).and_yield(@sqs_message)
|
225
|
+
BossQueue.work
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when something is dequeued from SQS" do
|
229
|
+
it "should use the dequeued id to retrieve a BossQueue::Job object" do
|
230
|
+
@queue.should_receive(:receive_message).and_yield(@sqs_message)
|
231
|
+
shard = double('shard')
|
232
|
+
BossQueue::Job.should_receive(:shard).with(BossQueue.table_name).and_return(shard)
|
233
|
+
shard.should_receive(:find_by_id).with('ijk').and_return(@job)
|
234
|
+
BossQueue.work
|
235
|
+
end
|
236
|
+
|
237
|
+
it "should call work on the BossQueue::Job object" do
|
238
|
+
@job.should_receive(:work)
|
239
|
+
BossQueue.work
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
data/spec/job_spec.rb
ADDED
@@ -0,0 +1,358 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe "BossQueue::Job" do
|
4
|
+
|
5
|
+
it "should respond to id" do
|
6
|
+
BossQueue::Job.new.should respond_to(:id)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should respond to id=" do
|
10
|
+
BossQueue::Job.new.should respond_to(:id=)
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
it "should respond to failed" do
|
15
|
+
BossQueue::Job.new.should respond_to(:failed)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should respond to failed=" do
|
19
|
+
BossQueue::Job.new.should respond_to(:failed=)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#failed" do
|
23
|
+
it "should default to false" do
|
24
|
+
BossQueue::Job.new.failed.should be_false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should respond to queue_name" do
|
29
|
+
BossQueue::Job.new.should respond_to(:queue_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should respond to queue_name=" do
|
33
|
+
BossQueue::Job.new.should respond_to(:queue_name=)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should respond to failed_attempts" do
|
37
|
+
BossQueue::Job.new.should respond_to(:failed_attempts)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should respond to failed_attempts=" do
|
41
|
+
BossQueue::Job.new.should respond_to(:failed_attempts=)
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
it "should respond to failure_action" do
|
46
|
+
BossQueue::Job.new.should respond_to(:failure_action)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should respond to failure_action=" do
|
50
|
+
BossQueue::Job.new.should respond_to(:failure_action=)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
it "should respond to exception_name" do
|
55
|
+
BossQueue::Job.new.should respond_to(:exception_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should respond to exception_name=" do
|
59
|
+
BossQueue::Job.new.should respond_to(:exception_name=)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
it "should respond to exception_message" do
|
64
|
+
BossQueue::Job.new.should respond_to(:exception_message)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should respond to exception_message=" do
|
68
|
+
BossQueue::Job.new.should respond_to(:exception_message=)
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
it "should respond to stacktrace" do
|
73
|
+
BossQueue::Job.new.should respond_to(:stacktrace)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should respond to stacktrace=" do
|
77
|
+
BossQueue::Job.new.should respond_to(:stacktrace=)
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
it "should respond to model_class_name" do
|
82
|
+
BossQueue::Job.new.should respond_to(:model_class_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should respond to model_class_name=" do
|
86
|
+
BossQueue::Job.new.should respond_to(:model_class_name=)
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
it "should respond to model_id" do
|
91
|
+
BossQueue::Job.new.should respond_to(:model_id)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should respond to model_id=" do
|
95
|
+
BossQueue::Job.new.should respond_to(:model_id=)
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
it "should respond to job_method" do
|
100
|
+
BossQueue::Job.new.should respond_to(:job_method)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should respond to job_method=" do
|
104
|
+
BossQueue::Job.new.should respond_to(:job_method=)
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it "should respond to job_arguments" do
|
109
|
+
BossQueue::Job.new.should respond_to(:job_arguments)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should respond to job_arguments=" do
|
113
|
+
BossQueue::Job.new.should respond_to(:job_arguments=)
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
describe "#work" do
|
118
|
+
before(:each) do
|
119
|
+
@job = BossQueue::Job.new
|
120
|
+
@job.stub(:destroy)
|
121
|
+
@job.model_class_name = 'TestClass'
|
122
|
+
@job.model_id = 'xyz'
|
123
|
+
@job.job_method = 'test_instance_method'
|
124
|
+
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
125
|
+
@argument_json = JSON.generate(@arguments)
|
126
|
+
@job.job_arguments = @argument_json
|
127
|
+
@instance_to_work_on = double('instance_to_work_on')
|
128
|
+
@instance_to_work_on.stub(:test_instance_method)
|
129
|
+
TestClass.stub(:find).and_return(@instance_to_work_on)
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
context "when model_id is not nil" do
|
134
|
+
it "should use #find on the model class to instantiate an object to work on" do
|
135
|
+
TestClass.should_receive(:find).with('xyz').and_return(@instance_to_work_on)
|
136
|
+
@job.work
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should pass the job arguments to the job method" do
|
140
|
+
@instance_to_work_on.should_receive(:test_instance_method).with('a', 'b', { 'c' => 2, 'd' => 1 })
|
141
|
+
@job.work
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when model_id is nil" do
|
146
|
+
before(:each) do
|
147
|
+
@job = BossQueue::Job.new
|
148
|
+
@job.stub(:destroy)
|
149
|
+
@job.model_class_name = 'TestClass'
|
150
|
+
@job.job_method = 'test_class_method'
|
151
|
+
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
152
|
+
@argument_json = JSON.generate(@arguments)
|
153
|
+
@job.job_arguments = @argument_json
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should pass the job arguments to the job method on the class" do
|
157
|
+
TestClass.should_receive(:test_class_method).with('a', 'b', { 'c' => 2, 'd' => 1 })
|
158
|
+
@job.work
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "when the job method doesn't raise an exception" do
|
163
|
+
it "should call destroy" do
|
164
|
+
@job.should_receive(:destroy)
|
165
|
+
@job.work
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
context "when the job method raises an exception" do
|
170
|
+
before(:each) do
|
171
|
+
@instance_to_work_on.stub(:test_instance_method).and_raise(StandardError.new)
|
172
|
+
@job.stub(:fail)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should call fail" do
|
176
|
+
@job.should_receive(:fail)
|
177
|
+
@job.work
|
178
|
+
end
|
179
|
+
|
180
|
+
it "should not call destroy" do
|
181
|
+
@job.should_not_receive(:destroy)
|
182
|
+
@job.work
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should not raise an exception" do
|
186
|
+
lambda {
|
187
|
+
@job.work
|
188
|
+
}.should_not raise_error
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "#enqueue" do
|
195
|
+
it "should enqueue id into the SQS queue" do
|
196
|
+
queue = double('queue')
|
197
|
+
AWS::SQS.stub_chain(:new, :queues, :[]).and_return(queue)
|
198
|
+
queue.should_receive(:send_message).with('ijk')
|
199
|
+
job = BossQueue::Job.new
|
200
|
+
job.id = 'ijk'
|
201
|
+
job.enqueue
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "#enqueue_with_delay" do
|
206
|
+
it "should enqueue id into the SQS queue with a delay" do
|
207
|
+
queue = double('queue')
|
208
|
+
AWS::SQS.stub_chain(:new, :queues, :[]).and_return(queue)
|
209
|
+
queue.should_receive(:send_message).with('ijk', :delay_seconds => 60)
|
210
|
+
job = BossQueue::Job.new
|
211
|
+
job.id = 'ijk'
|
212
|
+
job.enqueue_with_delay(60)
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should limit the delay to 15 minutes" do
|
216
|
+
queue = double('queue')
|
217
|
+
AWS::SQS.stub_chain(:new, :queues, :[]).and_return(queue)
|
218
|
+
queue.should_receive(:send_message).with('ijk', :delay_seconds => 900)
|
219
|
+
job = BossQueue::Job.new
|
220
|
+
job.id = 'ijk'
|
221
|
+
job.enqueue_with_delay(10000)
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should set a negative delay to 0" do
|
225
|
+
queue = double('queue')
|
226
|
+
AWS::SQS.stub_chain(:new, :queues, :[]).and_return(queue)
|
227
|
+
queue.should_receive(:send_message).with('ijk', :delay_seconds => 0)
|
228
|
+
job = BossQueue::Job.new
|
229
|
+
job.id = 'ijk'
|
230
|
+
job.enqueue_with_delay(-60)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe "#retry_delay" do
|
235
|
+
before(:each) do
|
236
|
+
@job = BossQueue::Job.new
|
237
|
+
end
|
238
|
+
|
239
|
+
context "when failed_attempts is nil" do
|
240
|
+
it "should be nil" do
|
241
|
+
@job.retry_delay.should == nil
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context "when failed_attempts is 1" do
|
246
|
+
it "should be 60" do
|
247
|
+
@job.failed_attempts = 1
|
248
|
+
@job.retry_delay.should == 60
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context "when failed_attempts is 2" do
|
253
|
+
it "should be 120" do
|
254
|
+
@job.failed_attempts = 2
|
255
|
+
@job.retry_delay.should == 120
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
context "when failed_attempts is 3" do
|
260
|
+
it "should be 240" do
|
261
|
+
@job.failed_attempts = 3
|
262
|
+
@job.retry_delay.should == 240
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context "when failed_attempts is 4" do
|
267
|
+
it "should be 480" do
|
268
|
+
@job.failed_attempts = 4
|
269
|
+
@job.retry_delay.should == 480
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
context "when failed_attempts greater than 4" do
|
274
|
+
it "should be nil ((60 + 120 + 240 + 480) == 900 (15 minutes), the maximum delay supported by SQS)" do
|
275
|
+
@job.failed_attempts = 5
|
276
|
+
@job.retry_delay.should be_nil
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe "#fail" do
|
282
|
+
before(:each) do
|
283
|
+
@job = BossQueue::Job.new
|
284
|
+
@job.stub(:retry_delay).and_return(nil)
|
285
|
+
@job.stub(:save!)
|
286
|
+
@job.stub(:enqueue_with_delay)
|
287
|
+
@err
|
288
|
+
begin
|
289
|
+
raise StandardError.new('hello world')
|
290
|
+
rescue StandardError => err
|
291
|
+
@err = err
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context "when failed_attempts is a number" do
|
296
|
+
it "should increment failed_attempts" do
|
297
|
+
@job.failed_attempts = 1
|
298
|
+
@job.fail(@err)
|
299
|
+
@job.failed_attempts.should == 2
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
it "should store the exception, message, and the first 7 lines of the stacktrace in the BossQueue::Job object" do
|
304
|
+
@job.fail(@err)
|
305
|
+
@job.exception_name.should == @err.class.to_s
|
306
|
+
@job.exception_message.should == @err.message
|
307
|
+
@job.stacktrace.should == @err.backtrace[0, 7].join("\n")
|
308
|
+
end
|
309
|
+
|
310
|
+
it "should call save!" do
|
311
|
+
@job.should_receive(:save!)
|
312
|
+
@job.fail(@err)
|
313
|
+
end
|
314
|
+
|
315
|
+
context "when failure_action is 'retry'" do
|
316
|
+
before(:each) do
|
317
|
+
@job.failure_action = 'retry'
|
318
|
+
end
|
319
|
+
|
320
|
+
context "when retry_delay returns a number" do
|
321
|
+
it "should re-enqueue with that delay" do
|
322
|
+
@job.stub(:retry_delay).and_return(60)
|
323
|
+
@job.should_receive(:enqueue_with_delay).with(60)
|
324
|
+
@job.fail(@err)
|
325
|
+
end
|
326
|
+
|
327
|
+
context "when failed_attempts is nil" do
|
328
|
+
it "should set failed_attempts to 1" do
|
329
|
+
@job.fail(@err)
|
330
|
+
@job.failed_attempts.should == 1
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
context "when retry_delay returns nil" do
|
336
|
+
it "should not re-enqueue" do
|
337
|
+
@job.should_not_receive(:enqueue)
|
338
|
+
@job.should_not_receive(:enqueue_with_delay)
|
339
|
+
@job.fail(@err)
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should set failed to true" do
|
343
|
+
@job.fail(@err)
|
344
|
+
@job.failed.should be_true
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
context "when failure_action is not 'retry'" do
|
350
|
+
it "should not re-enqueue" do
|
351
|
+
@job.should_not_receive(:enqueue)
|
352
|
+
@job.should_not_receive(:enqueue_with_delay)
|
353
|
+
@job.fail(@err)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'bundler'
|
5
|
+
Bundler.require
|
6
|
+
require 'pry'
|
7
|
+
require 'pry-nav'
|
8
|
+
require 'pry-stack_explorer'
|
9
|
+
|
10
|
+
require 'boss_queue'
|
11
|
+
|
12
|
+
# Requires supporting files with custom matchers and macros, etc,
|
13
|
+
# in ./support/ and its subdirectories.
|
14
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
|
18
|
+
|
19
|
+
class TestClass
|
20
|
+
def id
|
21
|
+
'xyz'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.test_class_method
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_instance_method
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: boss_queue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Daniel Nelson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: jeweler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: pry
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: pry-nav
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: pry-stack_explorer
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
description: A fault tolerant job queue built around Amazon SQS & DynamoDB
|
127
|
+
email: daniel@populr.me
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files:
|
131
|
+
- LICENSE
|
132
|
+
- README.md
|
133
|
+
files:
|
134
|
+
- .rspec
|
135
|
+
- Gemfile
|
136
|
+
- Gemfile.lock
|
137
|
+
- LICENSE
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- VERSION
|
141
|
+
- lib/boss_queue.rb
|
142
|
+
- lib/boss_queue/boss_queue.rb
|
143
|
+
- lib/boss_queue/job.rb
|
144
|
+
- spec/boss_queue_spec.rb
|
145
|
+
- spec/job_spec.rb
|
146
|
+
- spec/spec_helper.rb
|
147
|
+
homepage: https://github.com/populr/boss_queue
|
148
|
+
licenses:
|
149
|
+
- MIT
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ! '>='
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
hash: 1086179720775375384
|
163
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
|
+
none: false
|
165
|
+
requirements:
|
166
|
+
- - ! '>='
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 1.8.25
|
172
|
+
signing_key:
|
173
|
+
specification_version: 3
|
174
|
+
summary: A fault tolerant job queue built around Amazon SQS & DynamoDB
|
175
|
+
test_files: []
|