que 0.11.6 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -1
- data/docs/error_handling.md +2 -2
- data/lib/que.rb +11 -1
- data/lib/que/job.rb +37 -7
- data/lib/que/sql.rb +7 -7
- data/lib/que/version.rb +1 -1
- data/spec/unit/work_spec.rb +161 -15
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dd0f74eb9d94a2f949174b802fdd70f898cfe2e3
|
4
|
+
data.tar.gz: 60c5ea6a16dd1080bc16fcea19250c01e94937f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdc55d830dccfb0eaed7b7220b2bb65a239b0abda3b7cbd749cd87f7835fc6e521db375ef749e0a3f216f82620b6e76fc6dd6edf3b44c5d80f35feb8a43324ea
|
7
|
+
data.tar.gz: cecc9a9a728cf2445ffc4fed02c050972e8903cd4639594b069252e10f130c8dc96ea7bf2305c81f7a2b3e045e416d13198c3009cc08074d4545ebd232ef1f18
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
### 0.12.0 (2016-09-09)
|
2
|
+
|
3
|
+
* The error_handler configuration option has been renamed to error_notifier, which is more descriptive of what it's actually supposed to do. You can still use error_handler for configuration, but you'll get a warning.
|
4
|
+
|
5
|
+
* Introduced a new framework for handling errors on a per-job basis. See the docs for more information. (#106, #147)
|
6
|
+
|
1
7
|
### 0.11.6 (2016-07-01)
|
2
8
|
|
3
9
|
* Fix for operating in nested transactions in Rails 5.0. (#160) (greysteil)
|
@@ -62,7 +68,7 @@
|
|
62
68
|
|
63
69
|
### 0.9.0 (2014-12-16)
|
64
70
|
|
65
|
-
* The error_handler callable is now
|
71
|
+
* The error_handler callable is now passed two objects, the error and the job that raised it. If your current error_handler is a proc, as recommended in the docs, you shouldn't need to make any code changes, unless you want to use the job in your error handling. If your error_handler is a lambda, or another callable with a strict arity requirement, you'll want to change it before upgrading. (#69) (statianzo)
|
66
72
|
|
67
73
|
### 0.8.2 (2014-10-12)
|
68
74
|
|
data/docs/error_handling.md
CHANGED
@@ -20,10 +20,10 @@ end
|
|
20
20
|
|
21
21
|
Unlike DelayedJob, however, there is currently no maximum number of failures after which jobs will be deleted. Que's assumption is that if a job is erroring perpetually (and not just transiently), you will want to take action to get the job working properly rather than simply losing it silently.
|
22
22
|
|
23
|
-
If you're using an error notification system (highly recommended, of course), you can hook Que into it by setting a callable as the error
|
23
|
+
If you're using an error notification system (highly recommended, of course), you can hook Que into it by setting a callable as the error notifier:
|
24
24
|
|
25
25
|
```ruby
|
26
|
-
Que.
|
26
|
+
Que.error_notifier = proc do |error, job|
|
27
27
|
# Do whatever you want with the error object or job row here.
|
28
28
|
|
29
29
|
# Note that the job passed is not the actual job object, but the hash
|
data/lib/que.rb
CHANGED
@@ -48,7 +48,7 @@ module Que
|
|
48
48
|
end
|
49
49
|
|
50
50
|
class << self
|
51
|
-
attr_accessor :
|
51
|
+
attr_accessor :error_notifier
|
52
52
|
attr_writer :logger, :adapter, :log_formatter, :disable_prepared_statements, :json_converter
|
53
53
|
|
54
54
|
def connection=(connection)
|
@@ -131,6 +131,16 @@ module Que
|
|
131
131
|
@disable_prepared_statements || false
|
132
132
|
end
|
133
133
|
|
134
|
+
def error_handler
|
135
|
+
warn "Que.error_handler has been renamed to Que.error_notifier, please update your code. This shim will be removed in Que version 1.0.0."
|
136
|
+
error_notifier
|
137
|
+
end
|
138
|
+
|
139
|
+
def error_handler=(p)
|
140
|
+
warn "Que.error_handler= has been renamed to Que.error_notifier=, please update your code. This shim will be removed in Que version 1.0.0."
|
141
|
+
self.error_notifier = p
|
142
|
+
end
|
143
|
+
|
134
144
|
def constantize(camel_cased_word)
|
135
145
|
if camel_cased_word.respond_to?(:constantize)
|
136
146
|
# Use ActiveSupport's version if it exists.
|
data/lib/que/job.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Que
|
4
4
|
class Job
|
5
|
-
attr_reader :attrs
|
5
|
+
attr_reader :attrs, :_error
|
6
6
|
|
7
7
|
def initialize(attrs)
|
8
8
|
@attrs = attrs
|
@@ -16,10 +16,35 @@ module Que
|
|
16
16
|
def _run
|
17
17
|
run(*attrs[:args])
|
18
18
|
destroy unless @destroyed
|
19
|
+
rescue => error
|
20
|
+
@_error = error
|
21
|
+
run_error_notifier = handle_error(error)
|
22
|
+
destroy unless @retried || @destroyed
|
23
|
+
|
24
|
+
if run_error_notifier && Que.error_notifier
|
25
|
+
# Protect the work loop from a failure of the error notifier.
|
26
|
+
Que.error_notifier.call(error, @attrs) rescue nil
|
27
|
+
end
|
19
28
|
end
|
20
29
|
|
21
30
|
private
|
22
31
|
|
32
|
+
def error_count
|
33
|
+
@attrs[:error_count]
|
34
|
+
end
|
35
|
+
|
36
|
+
def handle_error(error)
|
37
|
+
error_count = @attrs[:error_count] += 1
|
38
|
+
retry_interval = self.class.retry_interval || Job.retry_interval
|
39
|
+
wait = retry_interval.respond_to?(:call) ? retry_interval.call(error_count) : retry_interval
|
40
|
+
retry_in(wait)
|
41
|
+
end
|
42
|
+
|
43
|
+
def retry_in(period)
|
44
|
+
Que.execute :set_error, [period, @_error.message] + @attrs.values_at(:queue, :priority, :run_at, :job_id)
|
45
|
+
@retried = true
|
46
|
+
end
|
47
|
+
|
23
48
|
def destroy
|
24
49
|
Que.execute :destroy_job, attrs.values_at(:queue, :priority, :run_at, :job_id)
|
25
50
|
@destroyed = true
|
@@ -99,8 +124,13 @@ module Que
|
|
99
124
|
{:event => :job_race_condition}
|
100
125
|
else
|
101
126
|
klass = class_for(job[:job_class])
|
102
|
-
klass.new(job)
|
103
|
-
|
127
|
+
instance = klass.new(job)
|
128
|
+
instance._run
|
129
|
+
if e = instance._error
|
130
|
+
{:event => :job_errored, :job => job, :error => e}
|
131
|
+
else
|
132
|
+
{:event => :job_worked, :job => job}
|
133
|
+
end
|
104
134
|
end
|
105
135
|
else
|
106
136
|
{:event => :job_unavailable}
|
@@ -112,16 +142,16 @@ module Que
|
|
112
142
|
interval = klass && klass.respond_to?(:retry_interval) && klass.retry_interval || retry_interval
|
113
143
|
delay = interval.respond_to?(:call) ? interval.call(count) : interval
|
114
144
|
message = "#{error.message}\n#{error.backtrace.join("\n")}"
|
115
|
-
Que.execute :set_error, [
|
145
|
+
Que.execute :set_error, [delay, message] + job.values_at(:queue, :priority, :run_at, :job_id)
|
116
146
|
end
|
117
147
|
rescue
|
118
148
|
# If we can't reach the database for some reason, too bad, but
|
119
149
|
# don't let it crash the work loop.
|
120
150
|
end
|
121
151
|
|
122
|
-
if Que.
|
123
|
-
# Similarly, protect the work loop from a failure of the error
|
124
|
-
Que.
|
152
|
+
if Que.error_notifier
|
153
|
+
# Similarly, protect the work loop from a failure of the error notifier.
|
154
|
+
Que.error_notifier.call(error, job) rescue nil
|
125
155
|
end
|
126
156
|
|
127
157
|
return {:event => :job_errored, :error => error, :job => job}
|
data/lib/que/sql.rb
CHANGED
@@ -88,13 +88,13 @@ module Que
|
|
88
88
|
|
89
89
|
:set_error => %{
|
90
90
|
UPDATE que_jobs
|
91
|
-
SET error_count =
|
92
|
-
run_at = now() + $
|
93
|
-
last_error = $
|
94
|
-
WHERE queue = $
|
95
|
-
AND priority = $
|
96
|
-
AND run_at = $
|
97
|
-
AND job_id = $
|
91
|
+
SET error_count = error_count + 1,
|
92
|
+
run_at = now() + $1::bigint * '1 second'::interval,
|
93
|
+
last_error = $2::text
|
94
|
+
WHERE queue = $3::text
|
95
|
+
AND priority = $4::smallint
|
96
|
+
AND run_at = $5::timestamptz
|
97
|
+
AND job_id = $6::bigint
|
98
98
|
}.freeze,
|
99
99
|
|
100
100
|
:insert_job => %{
|
data/lib/que/version.rb
CHANGED
data/spec/unit/work_spec.rb
CHANGED
@@ -237,7 +237,7 @@ describe Que::Job, '.work' do
|
|
237
237
|
DB[:que_jobs].count.should be 1
|
238
238
|
job = DB[:que_jobs].first
|
239
239
|
job[:error_count].should be 1
|
240
|
-
job[:last_error].should =~ /\AErrorJob
|
240
|
+
job[:last_error].should =~ /\AErrorJob!/
|
241
241
|
job[:run_at].should be_within(3).of Time.now + 4
|
242
242
|
|
243
243
|
DB[:que_jobs].update :error_count => 5,
|
@@ -251,7 +251,7 @@ describe Que::Job, '.work' do
|
|
251
251
|
DB[:que_jobs].count.should be 1
|
252
252
|
job = DB[:que_jobs].first
|
253
253
|
job[:error_count].should be 6
|
254
|
-
job[:last_error].should =~ /\AErrorJob
|
254
|
+
job[:last_error].should =~ /\AErrorJob!/
|
255
255
|
job[:run_at].should be_within(3).of Time.now + 1299
|
256
256
|
end
|
257
257
|
|
@@ -270,7 +270,7 @@ describe Que::Job, '.work' do
|
|
270
270
|
DB[:que_jobs].count.should be 1
|
271
271
|
job = DB[:que_jobs].first
|
272
272
|
job[:error_count].should be 1
|
273
|
-
job[:last_error].should =~ /\AErrorJob
|
273
|
+
job[:last_error].should =~ /\AErrorJob!/
|
274
274
|
job[:run_at].to_f.should be_within(3).of Time.now.to_f + RetryIntervalJob.retry_interval
|
275
275
|
|
276
276
|
DB[:que_jobs].update :error_count => 5,
|
@@ -284,7 +284,7 @@ describe Que::Job, '.work' do
|
|
284
284
|
DB[:que_jobs].count.should be 1
|
285
285
|
job = DB[:que_jobs].first
|
286
286
|
job[:error_count].should be 6
|
287
|
-
job[:last_error].should =~ /\AErrorJob
|
287
|
+
job[:last_error].should =~ /\AErrorJob!/
|
288
288
|
job[:run_at].to_f.should be_within(3).of Time.now.to_f + RetryIntervalJob.retry_interval
|
289
289
|
end
|
290
290
|
|
@@ -303,7 +303,7 @@ describe Que::Job, '.work' do
|
|
303
303
|
DB[:que_jobs].count.should be 1
|
304
304
|
job = DB[:que_jobs].first
|
305
305
|
job[:error_count].should be 1
|
306
|
-
job[:last_error].should =~ /\AErrorJob
|
306
|
+
job[:last_error].should =~ /\AErrorJob!/
|
307
307
|
job[:run_at].should be_within(3).of Time.now + 10
|
308
308
|
|
309
309
|
DB[:que_jobs].update :error_count => 5,
|
@@ -317,14 +317,14 @@ describe Que::Job, '.work' do
|
|
317
317
|
DB[:que_jobs].count.should be 1
|
318
318
|
job = DB[:que_jobs].first
|
319
319
|
job[:error_count].should be 6
|
320
|
-
job[:last_error].should =~ /\AErrorJob
|
320
|
+
job[:last_error].should =~ /\AErrorJob!/
|
321
321
|
job[:run_at].should be_within(3).of Time.now + 60
|
322
322
|
end
|
323
323
|
|
324
|
-
it "should pass it to an error
|
324
|
+
it "should pass it to an error notifier, if one is defined" do
|
325
325
|
begin
|
326
326
|
errors = []
|
327
|
-
Que.
|
327
|
+
Que.error_notifier = proc { |error| errors << error }
|
328
328
|
|
329
329
|
ErrorJob.enqueue
|
330
330
|
|
@@ -338,14 +338,14 @@ describe Que::Job, '.work' do
|
|
338
338
|
error.should be_an_instance_of RuntimeError
|
339
339
|
error.message.should == "ErrorJob!"
|
340
340
|
ensure
|
341
|
-
Que.
|
341
|
+
Que.error_notifier = nil
|
342
342
|
end
|
343
343
|
end
|
344
344
|
|
345
|
-
it "should pass job to an error
|
345
|
+
it "should pass job to an error notifier, if one is defined" do
|
346
346
|
begin
|
347
347
|
jobs = []
|
348
|
-
Que.
|
348
|
+
Que.error_notifier = proc { |error, job| jobs << job }
|
349
349
|
|
350
350
|
ErrorJob.enqueue
|
351
351
|
result = Que::Job.work
|
@@ -354,20 +354,20 @@ describe Que::Job, '.work' do
|
|
354
354
|
job = jobs[0]
|
355
355
|
job.should be result[:job]
|
356
356
|
ensure
|
357
|
-
Que.
|
357
|
+
Que.error_notifier = nil
|
358
358
|
end
|
359
359
|
end
|
360
360
|
|
361
|
-
it "should not do anything if the error
|
361
|
+
it "should not do anything if the error notifier itelf throws an error" do
|
362
362
|
begin
|
363
|
-
Que.
|
363
|
+
Que.error_notifier = proc { |error| raise "Another error!" }
|
364
364
|
ErrorJob.enqueue
|
365
365
|
|
366
366
|
result = Que::Job.work
|
367
367
|
result[:event].should == :job_errored
|
368
368
|
result[:error].should be_an_instance_of RuntimeError
|
369
369
|
ensure
|
370
|
-
Que.
|
370
|
+
Que.error_notifier = nil
|
371
371
|
end
|
372
372
|
end
|
373
373
|
|
@@ -403,5 +403,151 @@ describe Que::Job, '.work' do
|
|
403
403
|
job[:error_count].should be 1
|
404
404
|
job[:run_at].should be_within(3).of Time.now + 4
|
405
405
|
end
|
406
|
+
|
407
|
+
context "in a job class that has a custom error handler" do
|
408
|
+
it "should allow it to schedule a retry after a specific interval" do
|
409
|
+
begin
|
410
|
+
error = nil
|
411
|
+
Que.error_notifier = proc { |e| error = e }
|
412
|
+
|
413
|
+
class CustomRetryIntervalJob < Que::Job
|
414
|
+
def run(*args)
|
415
|
+
raise "Blah!"
|
416
|
+
end
|
417
|
+
|
418
|
+
private
|
419
|
+
|
420
|
+
def handle_error(error)
|
421
|
+
retry_in(42)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
CustomRetryIntervalJob.enqueue
|
426
|
+
|
427
|
+
result = Que::Job.work
|
428
|
+
result[:event].should == :job_errored
|
429
|
+
result[:error].should be_an_instance_of RuntimeError
|
430
|
+
result[:job][:job_class].should == 'CustomRetryIntervalJob'
|
431
|
+
|
432
|
+
DB[:que_jobs].count.should be 1
|
433
|
+
job = DB[:que_jobs].first
|
434
|
+
job[:error_count].should be 1
|
435
|
+
job[:last_error].should =~ /\ABlah!/
|
436
|
+
job[:run_at].should be_within(3).of Time.now + 42
|
437
|
+
|
438
|
+
error.should == result[:error]
|
439
|
+
ensure
|
440
|
+
Que.error_notifier = nil
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
it "should allow it to destroy the job" do
|
445
|
+
begin
|
446
|
+
error = nil
|
447
|
+
Que.error_notifier = proc { |e| error = e }
|
448
|
+
|
449
|
+
class CustomRetryIntervalJob < Que::Job
|
450
|
+
def run(*args)
|
451
|
+
raise "Blah!"
|
452
|
+
end
|
453
|
+
|
454
|
+
private
|
455
|
+
|
456
|
+
def handle_error(error)
|
457
|
+
destroy
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
CustomRetryIntervalJob.enqueue
|
462
|
+
|
463
|
+
result = Que::Job.work
|
464
|
+
result[:event].should == :job_errored
|
465
|
+
result[:error].should be_an_instance_of RuntimeError
|
466
|
+
result[:job][:job_class].should == 'CustomRetryIntervalJob'
|
467
|
+
|
468
|
+
DB[:que_jobs].count.should be 0
|
469
|
+
|
470
|
+
error.should == result[:error]
|
471
|
+
ensure
|
472
|
+
Que.error_notifier = nil
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
it "should allow it to return false to skip the error notification" do
|
477
|
+
begin
|
478
|
+
error = nil
|
479
|
+
Que.error_notifier = proc { |e| error = e }
|
480
|
+
|
481
|
+
class CustomRetryIntervalJob < Que::Job
|
482
|
+
def run(*args)
|
483
|
+
raise "Blah!"
|
484
|
+
end
|
485
|
+
|
486
|
+
private
|
487
|
+
|
488
|
+
def handle_error(error)
|
489
|
+
false
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
CustomRetryIntervalJob.enqueue
|
494
|
+
|
495
|
+
result = Que::Job.work
|
496
|
+
result[:event].should == :job_errored
|
497
|
+
result[:error].should be_an_instance_of RuntimeError
|
498
|
+
result[:job][:job_class].should == 'CustomRetryIntervalJob'
|
499
|
+
|
500
|
+
DB[:que_jobs].count.should be 0
|
501
|
+
|
502
|
+
error.should == nil
|
503
|
+
ensure
|
504
|
+
Que.error_notifier = nil
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
it "should allow it to call super to get the default behavior" do
|
509
|
+
begin
|
510
|
+
error = nil
|
511
|
+
Que.error_notifier = proc { |e| error = e }
|
512
|
+
|
513
|
+
class CustomRetryIntervalJob < Que::Job
|
514
|
+
def run(*args)
|
515
|
+
raise "Blah!"
|
516
|
+
end
|
517
|
+
|
518
|
+
private
|
519
|
+
|
520
|
+
def handle_error(error)
|
521
|
+
case error
|
522
|
+
when RuntimeError
|
523
|
+
super
|
524
|
+
else
|
525
|
+
$error_handler_failed = true
|
526
|
+
raise "Bad!"
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
CustomRetryIntervalJob.enqueue
|
532
|
+
|
533
|
+
result = Que::Job.work
|
534
|
+
result[:event].should == :job_errored
|
535
|
+
result[:error].should be_an_instance_of RuntimeError
|
536
|
+
result[:job][:job_class].should == 'CustomRetryIntervalJob'
|
537
|
+
|
538
|
+
$error_handler_failed.should == nil
|
539
|
+
|
540
|
+
DB[:que_jobs].count.should be 1
|
541
|
+
job = DB[:que_jobs].first
|
542
|
+
job[:error_count].should be 1
|
543
|
+
job[:last_error].should =~ /\ABlah!/
|
544
|
+
job[:run_at].should be_within(3).of Time.now + 4
|
545
|
+
|
546
|
+
error.should == result[:error]
|
547
|
+
ensure
|
548
|
+
Que.error_notifier = nil
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
406
552
|
end
|
407
553
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: que
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Hanks
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|