boss_queue 0.2.4 → 0.3.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/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:
|