boss_queue 0.2.4 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -1
- data/VERSION +1 -1
- data/boss_queue.gemspec +2 -2
- data/lib/boss_queue/boss_queue.rb +13 -9
- data/lib/boss_queue/job.rb +25 -13
- data/spec/boss_queue_spec.rb +28 -15
- data/spec/job_spec.rb +53 -12
- metadata +3 -3
data/README.md
CHANGED
@@ -67,6 +67,9 @@ Usage
|
|
67
67
|
myobject = MyClass.new
|
68
68
|
# default failure action is 'retry' which retries up to four times
|
69
69
|
queue = BossQueue.new(:failure_action => 'none', :queue => 'emails')
|
70
|
+
# or we can set up a callback method to be called on the enqueued class / object
|
71
|
+
# when there is a failure
|
72
|
+
queue = BossQueue.new(:failure_action => 'callback', :failure_callback => :method_to_execute_on_failure, :queue => 'emails')
|
70
73
|
|
71
74
|
# can enqueue instance methods (assumes that objects have an id and a #find(id) method)
|
72
75
|
queue.enqueue(myobject, :method_to_execute, arg1, arg2)
|
@@ -82,7 +85,7 @@ Usage
|
|
82
85
|
|
83
86
|
# failures are left in the DynamoDB table with the failed boolean set to true
|
84
87
|
|
85
|
-
BossQueue does not at present have a daemon component
|
88
|
+
BossQueue does not at present have a daemon component like Sidekiq and Resque do.
|
86
89
|
|
87
90
|
|
88
91
|
Future Work
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/boss_queue.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "boss_queue"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.3.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Daniel Nelson"]
|
12
|
-
s.date = "2013-
|
12
|
+
s.date = "2013-10-03"
|
13
13
|
s.description = "A fault tolerant job queue built around Amazon SQS & DynamoDB"
|
14
14
|
s.email = "daniel@populr.me"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -5,6 +5,7 @@ class BossQueue
|
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
7
|
@failure_action = options[:failure_action] if options[:failure_action]
|
8
|
+
@failure_callback = options[:failure_callback] if options[:failure_callback]
|
8
9
|
@queue_postfix = options[:queue] ? '_' + options[:queue] : ''
|
9
10
|
end
|
10
11
|
|
@@ -16,6 +17,10 @@ class BossQueue
|
|
16
17
|
@failure_action ||= 'retry'
|
17
18
|
end
|
18
19
|
|
20
|
+
def failure_callback
|
21
|
+
@failure_callback
|
22
|
+
end
|
23
|
+
|
19
24
|
def table_name
|
20
25
|
"#{BossQueue.queue_prefix}boss_queue_jobs"
|
21
26
|
end
|
@@ -57,33 +62,32 @@ class BossQueue
|
|
57
62
|
job_dequeued
|
58
63
|
end
|
59
64
|
|
60
|
-
def enqueue(class_or_instance,
|
61
|
-
job = create_job(class_or_instance,
|
65
|
+
def enqueue(class_or_instance, callback_method, *args)
|
66
|
+
job = create_job(class_or_instance, callback_method, *args)
|
62
67
|
job.enqueue
|
63
68
|
end
|
64
69
|
|
65
|
-
def enqueue_with_delay(delay, class_or_instance,
|
66
|
-
job = create_job(class_or_instance,
|
70
|
+
def enqueue_with_delay(delay, class_or_instance, callback_method, *args)
|
71
|
+
job = create_job(class_or_instance, callback_method, *args)
|
67
72
|
job.enqueue_with_delay(delay)
|
68
73
|
end
|
69
74
|
|
70
|
-
def create_job(class_or_instance,
|
75
|
+
def create_job(class_or_instance, callback_method, *args) # :nodoc:
|
71
76
|
job = BossQueue::Job.shard(table_name).new
|
72
77
|
if class_or_instance.is_a?(Class)
|
73
78
|
class_name = class_or_instance.to_s
|
74
79
|
instance_id = nil
|
75
|
-
job.kind = "#{class_name}@#{method_name}"
|
76
80
|
else
|
77
81
|
class_name = class_or_instance.class.to_s
|
78
82
|
instance_id = class_or_instance.id
|
79
|
-
job.kind = "#{class_name}##{method_name}"
|
80
83
|
end
|
81
84
|
job.queue_name = queue_name
|
82
85
|
job.failure_action = failure_action
|
86
|
+
job.failure_callback = failure_callback.to_s if failure_action == 'callback' && failure_callback
|
83
87
|
job.model_class_name = class_name
|
84
88
|
job.model_id = instance_id unless instance_id.nil?
|
85
|
-
job.
|
86
|
-
job.
|
89
|
+
job.callback = callback_method.to_s
|
90
|
+
job.args = JSON.generate(args)
|
87
91
|
job.save!
|
88
92
|
job
|
89
93
|
end
|
data/lib/boss_queue/job.rb
CHANGED
@@ -5,16 +5,16 @@ class BossQueue
|
|
5
5
|
class Job < AWS::Record::HashModel
|
6
6
|
attr_accessor :queue_name
|
7
7
|
|
8
|
-
string_attr :kind # an index based model_class_name, job_method
|
9
8
|
boolean_attr :failed
|
10
9
|
|
11
10
|
string_attr :model_class_name
|
12
11
|
string_attr :model_id
|
13
|
-
string_attr :
|
14
|
-
string_attr :
|
12
|
+
string_attr :callback
|
13
|
+
string_attr :args
|
15
14
|
|
16
15
|
integer_attr :failed_attempts
|
17
16
|
string_attr :failure_action
|
17
|
+
string_attr :failure_callback
|
18
18
|
string_attr :exception_name
|
19
19
|
string_attr :exception_message
|
20
20
|
string_attr :stacktrace
|
@@ -59,14 +59,7 @@ class BossQueue
|
|
59
59
|
|
60
60
|
def work
|
61
61
|
begin
|
62
|
-
|
63
|
-
if model_id
|
64
|
-
target = klass.find(model_id)
|
65
|
-
else
|
66
|
-
target = klass
|
67
|
-
end
|
68
|
-
args = JSON.parse(job_arguments)
|
69
|
-
target.send(job_method, *args)
|
62
|
+
target.send(callback, *arguments)
|
70
63
|
destroy
|
71
64
|
rescue StandardError => err
|
72
65
|
fail(err)
|
@@ -82,11 +75,17 @@ class BossQueue
|
|
82
75
|
|
83
76
|
if failure_action == 'retry' && retry_delay
|
84
77
|
enqueue_with_delay(retry_delay)
|
78
|
+
self.save!
|
79
|
+
|
80
|
+
elsif failure_action == 'callback' &&
|
81
|
+
failure_callback &&
|
82
|
+
target.send(failure_callback, *arguments)
|
83
|
+
destroy
|
84
|
+
|
85
85
|
else
|
86
86
|
self.failed = true
|
87
|
+
self.save!
|
87
88
|
end
|
88
|
-
|
89
|
-
self.save!
|
90
89
|
end
|
91
90
|
|
92
91
|
def retry_delay
|
@@ -100,6 +99,19 @@ class BossQueue
|
|
100
99
|
|
101
100
|
private
|
102
101
|
|
102
|
+
def arguments
|
103
|
+
JSON.parse(args)
|
104
|
+
end
|
105
|
+
|
106
|
+
def target
|
107
|
+
klass = constantize(model_class_name)
|
108
|
+
if model_id
|
109
|
+
klass.find(model_id)
|
110
|
+
else
|
111
|
+
klass
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
103
115
|
def sqs_queue
|
104
116
|
@sqs_queue ||= AWS::SQS.new.queues[AWS::SQS.new.queues.url_for(queue_name)]
|
105
117
|
end
|
data/spec/boss_queue_spec.rb
CHANGED
@@ -4,15 +4,16 @@ describe "BossQueue module" do
|
|
4
4
|
before(:each) do
|
5
5
|
BossQueue.environment = 'test'
|
6
6
|
|
7
|
-
BossQueue::Job.any_instance.stub(:kind=)
|
8
7
|
BossQueue::Job.any_instance.stub(:queue_name=)
|
9
8
|
BossQueue::Job.any_instance.stub(:failure_action=)
|
9
|
+
BossQueue::Job.any_instance.stub(:failure_callback=)
|
10
10
|
BossQueue::Job.any_instance.stub(:model_class_name=)
|
11
11
|
BossQueue::Job.any_instance.stub(:model_id=)
|
12
|
-
BossQueue::Job.any_instance.stub(:
|
13
|
-
BossQueue::Job.any_instance.stub(:
|
12
|
+
BossQueue::Job.any_instance.stub(:callback=)
|
13
|
+
BossQueue::Job.any_instance.stub(:args=)
|
14
14
|
BossQueue::Job.any_instance.stub(:save!)
|
15
15
|
BossQueue::Job.any_instance.stub(:enqueue)
|
16
|
+
BossQueue::Job.any_instance.stub(:enqueue_with_delay)
|
16
17
|
end
|
17
18
|
|
18
19
|
it "should respond to environment" do
|
@@ -146,13 +147,12 @@ describe "BossQueue module" do
|
|
146
147
|
context "when a class" do
|
147
148
|
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
148
149
|
queue = BossQueue.new
|
149
|
-
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass@test_class_method')
|
150
150
|
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
151
151
|
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
152
152
|
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
153
153
|
BossQueue::Job.any_instance.should_not_receive(:model_id=)
|
154
|
-
BossQueue::Job.any_instance.should_receive(:
|
155
|
-
BossQueue::Job.any_instance.should_receive(:
|
154
|
+
BossQueue::Job.any_instance.should_receive(:callback=).with('test_class_method')
|
155
|
+
BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
|
156
156
|
BossQueue::Job.any_instance.should_receive(:save!)
|
157
157
|
BossQueue::Job.any_instance.should_receive(:enqueue)
|
158
158
|
queue.enqueue(TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
@@ -162,19 +162,26 @@ describe "BossQueue module" do
|
|
162
162
|
context "when a class instance" do
|
163
163
|
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
164
164
|
queue = BossQueue.new
|
165
|
-
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass#test_instance_method')
|
166
165
|
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
167
166
|
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
168
167
|
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
169
168
|
BossQueue::Job.any_instance.should_receive(:model_id=).with('xyz')
|
170
|
-
BossQueue::Job.any_instance.should_receive(:
|
171
|
-
BossQueue::Job.any_instance.should_receive(:
|
169
|
+
BossQueue::Job.any_instance.should_receive(:callback=).with('test_instance_method')
|
170
|
+
BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
|
172
171
|
BossQueue::Job.any_instance.should_receive(:save!)
|
173
172
|
BossQueue::Job.any_instance.should_receive(:enqueue)
|
174
173
|
queue.enqueue(TestClass.new, :test_instance_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
175
174
|
end
|
176
175
|
end
|
177
176
|
|
177
|
+
context "when failure_action is 'callback'" do
|
178
|
+
it "should set the job failure_callback to the failure_callback option" do
|
179
|
+
queue = BossQueue.new(:failure_action => 'callback', :failure_callback => :call_if_failed)
|
180
|
+
BossQueue::Job.any_instance.should_receive(:failure_callback=).with('call_if_failed')
|
181
|
+
queue.enqueue(TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
178
185
|
end
|
179
186
|
|
180
187
|
describe "#enqueue_with_delay" do
|
@@ -186,13 +193,12 @@ describe "BossQueue module" do
|
|
186
193
|
context "when a class" do
|
187
194
|
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
188
195
|
queue = BossQueue.new
|
189
|
-
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass@test_class_method')
|
190
196
|
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
191
197
|
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
192
198
|
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
193
199
|
BossQueue::Job.any_instance.should_not_receive(:model_id=)
|
194
|
-
BossQueue::Job.any_instance.should_receive(:
|
195
|
-
BossQueue::Job.any_instance.should_receive(:
|
200
|
+
BossQueue::Job.any_instance.should_receive(:callback=).with('test_class_method')
|
201
|
+
BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
|
196
202
|
BossQueue::Job.any_instance.should_receive(:save!)
|
197
203
|
BossQueue::Job.any_instance.should_receive(:enqueue_with_delay).with(60)
|
198
204
|
queue.enqueue_with_delay(60, TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
@@ -202,19 +208,26 @@ describe "BossQueue module" do
|
|
202
208
|
context "when a class instance" do
|
203
209
|
it "should initialize a new BossQueue::Job object, save and call enqueue on it" do
|
204
210
|
queue = BossQueue.new
|
205
|
-
BossQueue::Job.any_instance.should_receive(:kind=).with('TestClass#test_instance_method')
|
206
211
|
BossQueue::Job.any_instance.should_receive(:queue_name=).with('test_boss_queue')
|
207
212
|
BossQueue::Job.any_instance.should_receive(:failure_action=).with('retry')
|
208
213
|
BossQueue::Job.any_instance.should_receive(:model_class_name=).with('TestClass')
|
209
214
|
BossQueue::Job.any_instance.should_receive(:model_id=).with('xyz')
|
210
|
-
BossQueue::Job.any_instance.should_receive(:
|
211
|
-
BossQueue::Job.any_instance.should_receive(:
|
215
|
+
BossQueue::Job.any_instance.should_receive(:callback=).with('test_instance_method')
|
216
|
+
BossQueue::Job.any_instance.should_receive(:args=).with(@argument_json)
|
212
217
|
BossQueue::Job.any_instance.should_receive(:save!)
|
213
218
|
BossQueue::Job.any_instance.should_receive(:enqueue_with_delay).with(60)
|
214
219
|
queue.enqueue_with_delay(60, TestClass.new, :test_instance_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
215
220
|
end
|
216
221
|
end
|
217
222
|
|
223
|
+
context "when failure_action is 'callback'" do
|
224
|
+
it "should set the job failure_callback to the failure_callback option" do
|
225
|
+
queue = BossQueue.new(:failure_action => 'callback', :failure_callback => :call_if_failed)
|
226
|
+
BossQueue::Job.any_instance.should_receive(:failure_callback=).with('call_if_failed')
|
227
|
+
queue.enqueue_with_delay(60, TestClass, :test_class_method, 'a', 'b', { 'c' => 2, 'd' => 1 })
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
218
231
|
end
|
219
232
|
|
220
233
|
describe "#work" do
|
data/spec/job_spec.rb
CHANGED
@@ -96,21 +96,21 @@ describe "BossQueue::Job" do
|
|
96
96
|
end
|
97
97
|
|
98
98
|
|
99
|
-
it "should respond to
|
100
|
-
BossQueue::Job.new.should respond_to(:
|
99
|
+
it "should respond to callback" do
|
100
|
+
BossQueue::Job.new.should respond_to(:callback)
|
101
101
|
end
|
102
102
|
|
103
|
-
it "should respond to
|
104
|
-
BossQueue::Job.new.should respond_to(:
|
103
|
+
it "should respond to callback=" do
|
104
|
+
BossQueue::Job.new.should respond_to(:callback=)
|
105
105
|
end
|
106
106
|
|
107
107
|
|
108
|
-
it "should respond to
|
109
|
-
BossQueue::Job.new.should respond_to(:
|
108
|
+
it "should respond to args" do
|
109
|
+
BossQueue::Job.new.should respond_to(:args)
|
110
110
|
end
|
111
111
|
|
112
|
-
it "should respond to
|
113
|
-
BossQueue::Job.new.should respond_to(:
|
112
|
+
it "should respond to args=" do
|
113
|
+
BossQueue::Job.new.should respond_to(:args=)
|
114
114
|
end
|
115
115
|
|
116
116
|
|
@@ -120,10 +120,10 @@ describe "BossQueue::Job" do
|
|
120
120
|
@job.stub(:destroy)
|
121
121
|
@job.model_class_name = 'TestClass'
|
122
122
|
@job.model_id = 'xyz'
|
123
|
-
@job.
|
123
|
+
@job.callback = 'test_instance_method'
|
124
124
|
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
125
125
|
@argument_json = JSON.generate(@arguments)
|
126
|
-
@job.
|
126
|
+
@job.args = @argument_json
|
127
127
|
@instance_to_work_on = double('instance_to_work_on')
|
128
128
|
@instance_to_work_on.stub(:test_instance_method)
|
129
129
|
TestClass.stub(:find).and_return(@instance_to_work_on)
|
@@ -147,10 +147,10 @@ describe "BossQueue::Job" do
|
|
147
147
|
@job = BossQueue::Job.new
|
148
148
|
@job.stub(:destroy)
|
149
149
|
@job.model_class_name = 'TestClass'
|
150
|
-
@job.
|
150
|
+
@job.callback = 'test_class_method'
|
151
151
|
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
152
152
|
@argument_json = JSON.generate(@arguments)
|
153
|
-
@job.
|
153
|
+
@job.args = @argument_json
|
154
154
|
end
|
155
155
|
|
156
156
|
it "should pass the job arguments to the job method on the class" do
|
@@ -316,6 +316,47 @@ describe "BossQueue::Job" do
|
|
316
316
|
@job.fail(@err)
|
317
317
|
end
|
318
318
|
|
319
|
+
context "when failure_action is 'callback'" do
|
320
|
+
before(:each) do
|
321
|
+
@job.failure_action = 'callback'
|
322
|
+
@job.failure_callback = 'failure'
|
323
|
+
|
324
|
+
@job.stub(:destroy)
|
325
|
+
@job.model_class_name = 'TestClass'
|
326
|
+
@job.model_id = 'xyz'
|
327
|
+
@job.callback = 'test_instance_method'
|
328
|
+
@arguments = ['a', 'b', { 'c' => 2, 'd' => 1 }]
|
329
|
+
@argument_json = JSON.generate(@arguments)
|
330
|
+
@job.args = @argument_json
|
331
|
+
@instance_to_work_on = double('instance_to_work_on')
|
332
|
+
@instance_to_work_on.stub(:test_instance_method)
|
333
|
+
@instance_to_work_on.stub(:failure).and_return(false)
|
334
|
+
TestClass.stub(:find).and_return(@instance_to_work_on)
|
335
|
+
end
|
336
|
+
|
337
|
+
it "should call the failure_callback method with the arguments" do
|
338
|
+
@instance_to_work_on.should_receive(:failure).with('a', 'b', { 'c' => 2, 'd' => 1 })
|
339
|
+
@job.fail(@err)
|
340
|
+
end
|
341
|
+
|
342
|
+
context "when the failure_callback method returns truthy" do
|
343
|
+
it "should delete the job" do
|
344
|
+
@job.should_receive(:destroy)
|
345
|
+
@instance_to_work_on.stub(:failure).and_return(true)
|
346
|
+
@job.fail(@err)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
context "when the failure_callback method returns falsey" do
|
351
|
+
it "should mark the job as failed" do
|
352
|
+
@job.should_not_receive(:destroy)
|
353
|
+
@instance_to_work_on.stub(:failure).and_return(false)
|
354
|
+
@job.fail(@err)
|
355
|
+
@job.failed.should be_true
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
359
|
+
|
319
360
|
context "when failure_action is 'retry'" do
|
320
361
|
before(:each) do
|
321
362
|
@job.failure_action = 'retry'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boss_queue
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-10-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -160,7 +160,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
160
160
|
version: '0'
|
161
161
|
segments:
|
162
162
|
- 0
|
163
|
-
hash:
|
163
|
+
hash: 2478012361978662154
|
164
164
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
165
|
none: false
|
166
166
|
requirements:
|