delayed_job 4.0.2 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -13
- data/CHANGELOG.md +7 -0
- data/README.md +12 -7
- data/Rakefile +6 -1
- data/delayed_job.gemspec +5 -5
- data/lib/delayed/backend/base.rb +18 -20
- data/lib/delayed/backend/shared_spec.rb +139 -138
- data/lib/delayed/command.rb +75 -40
- data/lib/delayed/exceptions.rb +2 -1
- data/lib/delayed/lifecycle.rb +12 -11
- data/lib/delayed/message_sending.rb +9 -10
- data/lib/delayed/performable_mailer.rb +2 -2
- data/lib/delayed/performable_method.rb +2 -2
- data/lib/delayed/psych_ext.rb +29 -93
- data/lib/delayed/recipes.rb +5 -5
- data/lib/delayed/serialization/active_record.rb +11 -9
- data/lib/delayed/syck_ext.rb +3 -3
- data/lib/delayed/tasks.rb +5 -5
- data/lib/delayed/worker.rb +42 -42
- data/lib/generators/delayed_job/delayed_job_generator.rb +2 -3
- data/spec/delayed/backend/test.rb +22 -17
- data/spec/delayed/command_spec.rb +57 -0
- data/spec/helper.rb +25 -12
- data/spec/lifecycle_spec.rb +23 -15
- data/spec/message_sending_spec.rb +34 -34
- data/spec/performable_mailer_spec.rb +11 -11
- data/spec/performable_method_spec.rb +24 -26
- data/spec/psych_ext_spec.rb +12 -0
- data/spec/sample_jobs.rb +46 -18
- data/spec/test_backend_spec.rb +3 -3
- data/spec/worker_spec.rb +27 -27
- data/spec/yaml_ext_spec.rb +16 -16
- metadata +12 -8
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
MDJmZDk3ZjJjYmNkZWFiMmIzNTU5MGQzMmNiOWM4Mzk2ODE0ODkzZg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f8464e2a78c559301360d0c083d940e73f314406
|
4
|
+
data.tar.gz: 819bd2dc775364ed9e6311be0a82fb3bdc20d6a6
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
ZWVkNjcxMWRlZmZmOTU4OGNiMTU0N2E2N2QwODczYmQyNjI5YmM3MDc3ZTNm
|
11
|
-
YTI2Njk1NzBjZDFmYTkxZmE1YmVkYzJkOWI3NDZiODc2NDc1NjM=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
OTBkOGMwYjNiZGIxNWMxODk3ZjI1OWFlZTU5NDlmNTZiZTBhNGQ0ZTY1ZjRm
|
14
|
-
ZWYwYzFkZjgxMTE5OWJhMzg3NWU0YzU3MWNhMGJmMjg0N2Y4NDk5MjY5ZDE3
|
15
|
-
NDMxYWFmOTU0M2FhZWJmMzIyYmNlMDZiYjJjODg0NjM0NWVkODQ=
|
6
|
+
metadata.gz: c7d9b76c5f6244b37e79f2df7f5d5d270bd0769366ab0d93967ca02bd9ff434b98801ed92032815d0e0dd1abba5f9617881dd64aeabf09ed0700066f59985a3f
|
7
|
+
data.tar.gz: 3f99c1bcddf3c835a4eea9cd200b559d1d284d169633016e79b0303549583be78cdfd888671139fe83ecec669b140171bb255d3f4bf03eaa9a4a45ed2c2bc7e2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
4.0.3 - 2014-09-04
|
2
|
+
==================
|
3
|
+
* Added --pools option to delayed_job command
|
4
|
+
* Removed a bunch of the Psych hacks
|
5
|
+
* Improved deserialization error reporting
|
6
|
+
* Misc bug fixes
|
7
|
+
|
1
8
|
4.0.2 - 2014-06-24
|
2
9
|
==================
|
3
10
|
* Add support for RSpec 3
|
data/README.md
CHANGED
@@ -190,6 +190,11 @@ You can then do the following:
|
|
190
190
|
RAILS_ENV=production script/delayed_job --queue=tracking start
|
191
191
|
RAILS_ENV=production script/delayed_job --queues=mailers,tasks start
|
192
192
|
|
193
|
+
# Use the --pool option to specify a worker pool. You can use this option multiple times to start different numbers of workers for different queues.
|
194
|
+
# The following command will start 1 worker for the tracking queue,
|
195
|
+
# 2 workers for the mailers and tasks queues, and 2 workers for any jobs:
|
196
|
+
RAILS_ENV=production script/delayed_job --pool=tracking --pool=mailers,tasks:2 --pool=*:2 start
|
197
|
+
|
193
198
|
# Runs all available jobs and then exits
|
194
199
|
RAILS_ENV=production script/delayed_job start --exit-on-complete
|
195
200
|
# or to run in the foreground
|
@@ -310,22 +315,22 @@ end
|
|
310
315
|
|
311
316
|
On failure, the job is scheduled again in 5 seconds + N ** 4, where N is the number of retries.
|
312
317
|
|
313
|
-
The default Worker.max_attempts is 25. After this, the job either deleted (default), or left in the database with "failed_at" set.
|
318
|
+
The default `Worker.max_attempts` is 25. After this, the job either deleted (default), or left in the database with "failed_at" set.
|
314
319
|
With the default of 25 attempts, the last retry will be 20 days later, with the last interval being almost 100 hours.
|
315
320
|
|
316
|
-
The default Worker.max_run_time is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to
|
321
|
+
The default `Worker.max_run_time` is 4.hours. If your job takes longer than that, another computer could pick it up. It's up to you to
|
317
322
|
make sure your job doesn't exceed this time. You should set this to the longest time you think the job could take.
|
318
323
|
|
319
324
|
By default, it will delete failed jobs (and it always deletes successful jobs). If you want to keep failed jobs, set
|
320
|
-
Delayed::Worker.destroy_failed_jobs = false
|
325
|
+
`Delayed::Worker.destroy_failed_jobs = false`. The failed jobs will be marked with non-null failed_at.
|
321
326
|
|
322
|
-
By default all jobs are scheduled with priority = 0
|
327
|
+
By default all jobs are scheduled with `priority = 0`, which is top priority. You can change this by setting `Delayed::Worker.default_priority` to something else. Lower numbers have higher priority.
|
323
328
|
|
324
|
-
The default behavior is to read 5 jobs from the queue when finding an available job. You can configure this by setting Delayed::Worker.read_ahead
|
329
|
+
The default behavior is to read 5 jobs from the queue when finding an available job. You can configure this by setting `Delayed::Worker.read_ahead`.
|
325
330
|
|
326
|
-
By default all jobs will be queued without a named queue. A default named queue can be specified by using Delayed::Worker.default_queue_name
|
331
|
+
By default all jobs will be queued without a named queue. A default named queue can be specified by using `Delayed::Worker.default_queue_name`.
|
327
332
|
|
328
|
-
It is possible to disable delayed jobs for testing purposes. Set Delayed::Worker.delay_jobs = false to execute all jobs realtime.
|
333
|
+
It is possible to disable delayed jobs for testing purposes. Set `Delayed::Worker.delay_jobs = false` to execute all jobs realtime.
|
329
334
|
|
330
335
|
Here is an example of changing job parameters in Rails:
|
331
336
|
|
data/Rakefile
CHANGED
data/delayed_job.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
|
-
spec.add_dependency
|
3
|
-
spec.authors = [
|
4
|
-
spec.description =
|
2
|
+
spec.add_dependency 'activesupport', ['>= 3.0', '< 4.2']
|
3
|
+
spec.authors = ['Brandon Keepers', 'Brian Ryckbost', 'Chris Gaffney', 'David Genord II', 'Erik Michaels-Ober', 'Matt Griffin', 'Steve Richert', 'Tobias Lütke']
|
4
|
+
spec.description = 'Delayed_job (or DJ) encapsulates the common pattern of asynchronously executing longer tasks in the background. It is a direct extraction from Shopify where the job table is responsible for a multitude of core tasks.'
|
5
5
|
spec.email = ['brian@collectiveidea.com']
|
6
|
-
spec.files = %w
|
6
|
+
spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile delayed_job.gemspec]
|
7
7
|
spec.files += Dir.glob('{contrib,lib,recipes,spec}/**/*')
|
8
8
|
spec.homepage = 'http://github.com/collectiveidea/delayed_job'
|
9
9
|
spec.licenses = ['MIT']
|
@@ -11,5 +11,5 @@ Gem::Specification.new do |spec|
|
|
11
11
|
spec.require_paths = ['lib']
|
12
12
|
spec.summary = 'Database-backed asynchronous priority queue system -- Extracted from Shopify'
|
13
13
|
spec.test_files = Dir.glob('spec/**/*')
|
14
|
-
spec.version = '4.0.
|
14
|
+
spec.version = '4.0.3'
|
15
15
|
end
|
data/lib/delayed/backend/base.rb
CHANGED
@@ -16,7 +16,7 @@ module Delayed
|
|
16
16
|
options[:payload_object] ||= args.shift
|
17
17
|
|
18
18
|
if args.size > 0
|
19
|
-
warn
|
19
|
+
warn '[DEPRECATION] Passing multiple arguments to `#enqueue` is deprecated. Pass a hash with :priority and :run_at.'
|
20
20
|
options[:priority] = args.first || options[:priority]
|
21
21
|
options[:run_at] = args[1]
|
22
22
|
end
|
@@ -26,7 +26,7 @@ module Delayed
|
|
26
26
|
end
|
27
27
|
|
28
28
|
if Delayed::Worker.delay_jobs
|
29
|
-
|
29
|
+
new(options).tap do |job|
|
30
30
|
Delayed::Worker.lifecycle.run_callbacks(:enqueue, job) do
|
31
31
|
job.hook(:enqueue)
|
32
32
|
job.save
|
@@ -48,7 +48,7 @@ module Delayed
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# Allow the backend to attempt recovery from reserve errors
|
51
|
-
def recover_from(
|
51
|
+
def recover_from(_error)
|
52
52
|
end
|
53
53
|
|
54
54
|
# Hook method that is called before a new worker is forked
|
@@ -60,7 +60,7 @@ module Delayed
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def work_off(num = 100)
|
63
|
-
warn
|
63
|
+
warn '[DEPRECATION] `Delayed::Job.work_off` is deprecated. Use `Delayed::Worker.new.work_off instead.'
|
64
64
|
Delayed::Worker.new.work_off(num)
|
65
65
|
end
|
66
66
|
end
|
@@ -70,12 +70,10 @@ module Delayed
|
|
70
70
|
end
|
71
71
|
alias_method :failed, :failed?
|
72
72
|
|
73
|
-
ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/
|
73
|
+
ParseObjectFromYaml = /\!ruby\/\w+\:([^\s]+)/ # rubocop:disable ConstantName
|
74
74
|
|
75
75
|
def name
|
76
|
-
@name ||= payload_object.respond_to?(:display_name) ?
|
77
|
-
payload_object.display_name :
|
78
|
-
payload_object.class.name
|
76
|
+
@name ||= payload_object.respond_to?(:display_name) ? payload_object.display_name : payload_object.class.name
|
79
77
|
rescue DeserializationError
|
80
78
|
ParseObjectFromYaml.match(handler)[1]
|
81
79
|
end
|
@@ -87,15 +85,14 @@ module Delayed
|
|
87
85
|
|
88
86
|
def payload_object
|
89
87
|
if YAML.respond_to?(:unsafe_load)
|
90
|
-
#See https://github.com/dtao/safe_yaml
|
91
|
-
#When the method is there, we need to load our YAML like this...
|
92
|
-
@payload_object ||= YAML.load(
|
88
|
+
# See https://github.com/dtao/safe_yaml
|
89
|
+
# When the method is there, we need to load our YAML like this...
|
90
|
+
@payload_object ||= YAML.load(handler, :safe => false)
|
93
91
|
else
|
94
|
-
@payload_object ||= YAML.load(
|
92
|
+
@payload_object ||= YAML.load(handler)
|
95
93
|
end
|
96
94
|
rescue TypeError, LoadError, NameError, ArgumentError => e
|
97
|
-
raise DeserializationError,
|
98
|
-
"Job failed to load: #{e.message}. Handler: #{handler.inspect}"
|
95
|
+
raise DeserializationError, "Job failed to load: #{e.message}. Handler: #{handler.inspect}"
|
99
96
|
end
|
100
97
|
|
101
98
|
def invoke_job
|
@@ -104,7 +101,7 @@ module Delayed
|
|
104
101
|
hook :before
|
105
102
|
payload_object.perform
|
106
103
|
hook :success
|
107
|
-
rescue
|
104
|
+
rescue => e
|
108
105
|
hook :error, e
|
109
106
|
raise e
|
110
107
|
ensure
|
@@ -124,14 +121,15 @@ module Delayed
|
|
124
121
|
method = payload_object.method(name)
|
125
122
|
method.arity == 0 ? method.call : method.call(self, *args)
|
126
123
|
end
|
127
|
-
rescue DeserializationError
|
128
|
-
# do nothing
|
124
|
+
rescue DeserializationError # rubocop:disable HandleExceptions
|
129
125
|
end
|
130
126
|
|
131
127
|
def reschedule_at
|
132
|
-
payload_object.respond_to?(:reschedule_at)
|
133
|
-
payload_object.reschedule_at(self.class.db_time_now, attempts)
|
134
|
-
|
128
|
+
if payload_object.respond_to?(:reschedule_at)
|
129
|
+
payload_object.reschedule_at(self.class.db_time_now, attempts)
|
130
|
+
else
|
131
|
+
self.class.db_time_now + (attempts**4) + 5
|
132
|
+
end
|
135
133
|
end
|
136
134
|
|
137
135
|
def max_attempts
|
@@ -2,7 +2,7 @@ require File.expand_path('../../../../spec/sample_jobs', __FILE__)
|
|
2
2
|
|
3
3
|
require 'active_support/core_ext'
|
4
4
|
|
5
|
-
shared_examples_for
|
5
|
+
shared_examples_for 'a delayed_job backend' do
|
6
6
|
let(:worker) { Delayed::Worker.new }
|
7
7
|
|
8
8
|
def create_job(opts = {})
|
@@ -22,74 +22,74 @@ shared_examples_for "a delayed_job backend" do
|
|
22
22
|
Delayed::Worker.reset
|
23
23
|
end
|
24
24
|
|
25
|
-
it
|
26
|
-
expect(described_class.create(:payload_object => ErrorJob.new
|
25
|
+
it 'sets run_at automatically if not set' do
|
26
|
+
expect(described_class.create(:payload_object => ErrorJob.new).run_at).not_to be_nil
|
27
27
|
end
|
28
28
|
|
29
|
-
it
|
29
|
+
it 'does not set run_at automatically if already set' do
|
30
30
|
later = described_class.db_time_now + 5.minutes
|
31
31
|
job = described_class.create(:payload_object => ErrorJob.new, :run_at => later)
|
32
32
|
expect(job.run_at).to be_within(1).of(later)
|
33
33
|
end
|
34
34
|
|
35
|
-
describe
|
36
|
-
it
|
35
|
+
describe '#reload' do
|
36
|
+
it 'reloads the payload' do
|
37
37
|
job = described_class.enqueue :payload_object => SimpleJob.new
|
38
38
|
expect(job.payload_object.object_id).not_to eq(job.reload.payload_object.object_id)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
describe
|
43
|
-
context
|
42
|
+
describe 'enqueue' do
|
43
|
+
context 'with a hash' do
|
44
44
|
it "raises ArgumentError when handler doesn't respond_to :perform" do
|
45
|
-
expect{described_class.enqueue(:payload_object => Object.new)}.to raise_error(ArgumentError)
|
45
|
+
expect { described_class.enqueue(:payload_object => Object.new) }.to raise_error(ArgumentError)
|
46
46
|
end
|
47
47
|
|
48
|
-
it
|
48
|
+
it 'is able to set priority' do
|
49
49
|
job = described_class.enqueue :payload_object => SimpleJob.new, :priority => 5
|
50
50
|
expect(job.priority).to eq(5)
|
51
51
|
end
|
52
52
|
|
53
|
-
it
|
53
|
+
it 'uses default priority' do
|
54
54
|
job = described_class.enqueue :payload_object => SimpleJob.new
|
55
55
|
expect(job.priority).to eq(99)
|
56
56
|
end
|
57
57
|
|
58
|
-
it
|
58
|
+
it 'is able to set run_at' do
|
59
59
|
later = described_class.db_time_now + 5.minutes
|
60
60
|
job = described_class.enqueue :payload_object => SimpleJob.new, :run_at => later
|
61
61
|
expect(job.run_at).to be_within(1).of(later)
|
62
62
|
end
|
63
63
|
|
64
|
-
it
|
64
|
+
it 'is able to set queue' do
|
65
65
|
job = described_class.enqueue :payload_object => SimpleJob.new, :queue => 'tracking'
|
66
66
|
expect(job.queue).to eq('tracking')
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
context
|
70
|
+
context 'with multiple arguments' do
|
71
71
|
it "raises ArgumentError when handler doesn't respond_to :perform" do
|
72
|
-
expect{described_class.enqueue(Object.new)}.to raise_error(ArgumentError)
|
72
|
+
expect { described_class.enqueue(Object.new) }.to raise_error(ArgumentError)
|
73
73
|
end
|
74
74
|
|
75
|
-
it
|
75
|
+
it 'increases count after enqueuing items' do
|
76
76
|
described_class.enqueue SimpleJob.new
|
77
77
|
expect(described_class.count).to eq(1)
|
78
78
|
end
|
79
79
|
|
80
|
-
it
|
80
|
+
it 'is able to set priority [DEPRECATED]' do
|
81
81
|
silence_warnings do
|
82
82
|
job = described_class.enqueue SimpleJob.new, 5
|
83
83
|
expect(job.priority).to eq(5)
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
-
it
|
87
|
+
it 'uses default priority when it is not set' do
|
88
88
|
@job = described_class.enqueue SimpleJob.new
|
89
89
|
expect(@job.priority).to eq(99)
|
90
90
|
end
|
91
91
|
|
92
|
-
it
|
92
|
+
it 'is able to set run_at [DEPRECATED]' do
|
93
93
|
silence_warnings do
|
94
94
|
later = described_class.db_time_now + 5.minutes
|
95
95
|
@job = described_class.enqueue SimpleJob.new, 5, later
|
@@ -97,41 +97,41 @@ shared_examples_for "a delayed_job backend" do
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
-
it
|
100
|
+
it 'works with jobs in modules' do
|
101
101
|
M::ModuleJob.runs = 0
|
102
102
|
job = described_class.enqueue M::ModuleJob.new
|
103
|
-
expect{job.invoke_job}.to change { M::ModuleJob.runs }.from(0).to(1)
|
103
|
+
expect { job.invoke_job }.to change { M::ModuleJob.runs }.from(0).to(1)
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
context
|
107
|
+
context 'with delay_jobs = false' do
|
108
108
|
before(:each) do
|
109
109
|
Delayed::Worker.delay_jobs = false
|
110
110
|
end
|
111
111
|
|
112
|
-
it
|
112
|
+
it 'does not increase count after enqueuing items' do
|
113
113
|
described_class.enqueue SimpleJob.new
|
114
114
|
expect(described_class.count).to eq(0)
|
115
115
|
end
|
116
116
|
|
117
|
-
it
|
117
|
+
it 'invokes the enqueued job' do
|
118
118
|
job = SimpleJob.new
|
119
119
|
expect(job).to receive(:perform)
|
120
120
|
described_class.enqueue job
|
121
121
|
end
|
122
122
|
|
123
|
-
it
|
123
|
+
it 'returns a job, not the result of invocation' do
|
124
124
|
expect(described_class.enqueue(SimpleJob.new)).to be_instance_of(described_class)
|
125
125
|
end
|
126
126
|
end
|
127
127
|
end
|
128
128
|
|
129
|
-
describe
|
129
|
+
describe 'callbacks' do
|
130
130
|
before(:each) do
|
131
131
|
CallbackJob.messages = []
|
132
132
|
end
|
133
133
|
|
134
|
-
%w
|
134
|
+
%w[before success after].each do |callback|
|
135
135
|
it "calls #{callback} with job" do
|
136
136
|
job = described_class.enqueue(CallbackJob.new)
|
137
137
|
expect(job.payload_object).to receive(callback).with(job)
|
@@ -139,48 +139,48 @@ shared_examples_for "a delayed_job backend" do
|
|
139
139
|
end
|
140
140
|
end
|
141
141
|
|
142
|
-
it
|
142
|
+
it 'calls before and after callbacks' do
|
143
143
|
job = described_class.enqueue(CallbackJob.new)
|
144
|
-
expect(CallbackJob.messages).to eq([
|
144
|
+
expect(CallbackJob.messages).to eq(['enqueue'])
|
145
145
|
job.invoke_job
|
146
|
-
expect(CallbackJob.messages).to eq([
|
146
|
+
expect(CallbackJob.messages).to eq(%w[enqueue before perform success after])
|
147
147
|
end
|
148
148
|
|
149
|
-
it
|
149
|
+
it 'calls the after callback with an error' do
|
150
150
|
job = described_class.enqueue(CallbackJob.new)
|
151
|
-
expect(job.payload_object).to receive(:perform).and_raise(RuntimeError.new(
|
151
|
+
expect(job.payload_object).to receive(:perform).and_raise(RuntimeError.new('fail'))
|
152
152
|
|
153
|
-
expect{job.invoke_job}.to raise_error
|
154
|
-
expect(CallbackJob.messages).to eq([
|
153
|
+
expect { job.invoke_job }.to raise_error
|
154
|
+
expect(CallbackJob.messages).to eq(['enqueue', 'before', 'error: RuntimeError', 'after'])
|
155
155
|
end
|
156
156
|
|
157
|
-
it
|
157
|
+
it 'calls error when before raises an error' do
|
158
158
|
job = described_class.enqueue(CallbackJob.new)
|
159
|
-
expect(job.payload_object).to receive(:before).and_raise(RuntimeError.new(
|
160
|
-
expect{job.invoke_job}.to raise_error(RuntimeError)
|
161
|
-
expect(CallbackJob.messages).to eq([
|
159
|
+
expect(job.payload_object).to receive(:before).and_raise(RuntimeError.new('fail'))
|
160
|
+
expect { job.invoke_job }.to raise_error(RuntimeError)
|
161
|
+
expect(CallbackJob.messages).to eq(['enqueue', 'error: RuntimeError', 'after'])
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
165
|
-
describe
|
166
|
-
it
|
167
|
-
job = described_class.new :handler =>
|
168
|
-
expect{job.payload_object}.to raise_error(Delayed::DeserializationError)
|
165
|
+
describe 'payload_object' do
|
166
|
+
it 'raises a DeserializationError when the job class is totally unknown' do
|
167
|
+
job = described_class.new :handler => '--- !ruby/object:JobThatDoesNotExist {}'
|
168
|
+
expect { job.payload_object }.to raise_error(Delayed::DeserializationError)
|
169
169
|
end
|
170
170
|
|
171
|
-
it
|
172
|
-
job = described_class.new :handler =>
|
173
|
-
expect{job.payload_object}.to raise_error(Delayed::DeserializationError)
|
171
|
+
it 'raises a DeserializationError when the job struct is totally unknown' do
|
172
|
+
job = described_class.new :handler => '--- !ruby/struct:StructThatDoesNotExist {}'
|
173
|
+
expect { job.payload_object }.to raise_error(Delayed::DeserializationError)
|
174
174
|
end
|
175
175
|
|
176
|
-
it
|
177
|
-
job = described_class.new :handler =>
|
176
|
+
it 'raises a DeserializationError when the YAML.load raises argument error' do
|
177
|
+
job = described_class.new :handler => '--- !ruby/struct:GoingToRaiseArgError {}'
|
178
178
|
expect(YAML).to receive(:load).and_raise(ArgumentError)
|
179
|
-
expect{job.payload_object}.to raise_error(Delayed::DeserializationError)
|
179
|
+
expect { job.payload_object }.to raise_error(Delayed::DeserializationError)
|
180
180
|
end
|
181
181
|
end
|
182
182
|
|
183
|
-
describe
|
183
|
+
describe 'reserve' do
|
184
184
|
before do
|
185
185
|
Delayed::Worker.max_run_time = 2.minutes
|
186
186
|
end
|
@@ -189,28 +189,28 @@ shared_examples_for "a delayed_job backend" do
|
|
189
189
|
Time.zone = nil
|
190
190
|
end
|
191
191
|
|
192
|
-
it
|
192
|
+
it 'does not reserve failed jobs' do
|
193
193
|
create_job :attempts => 50, :failed_at => described_class.db_time_now
|
194
194
|
expect(described_class.reserve(worker)).to be_nil
|
195
195
|
end
|
196
196
|
|
197
|
-
it
|
197
|
+
it 'does not reserve jobs scheduled for the future' do
|
198
198
|
create_job :run_at => described_class.db_time_now + 1.minute
|
199
199
|
expect(described_class.reserve(worker)).to be_nil
|
200
200
|
end
|
201
201
|
|
202
|
-
it
|
202
|
+
it 'reserves jobs scheduled for the past' do
|
203
203
|
job = create_job :run_at => described_class.db_time_now - 1.minute
|
204
204
|
expect(described_class.reserve(worker)).to eq(job)
|
205
205
|
end
|
206
206
|
|
207
|
-
it
|
207
|
+
it 'reserves jobs scheduled for the past when time zones are involved' do
|
208
208
|
Time.zone = 'US/Eastern'
|
209
209
|
job = create_job :run_at => described_class.db_time_now - 1.minute
|
210
210
|
expect(described_class.reserve(worker)).to eq(job)
|
211
211
|
end
|
212
212
|
|
213
|
-
it
|
213
|
+
it 'does not reserve jobs locked by other workers' do
|
214
214
|
job = create_job
|
215
215
|
other_worker = Delayed::Worker.new
|
216
216
|
other_worker.name = 'other_worker'
|
@@ -218,51 +218,51 @@ shared_examples_for "a delayed_job backend" do
|
|
218
218
|
expect(described_class.reserve(worker)).to be_nil
|
219
219
|
end
|
220
220
|
|
221
|
-
it
|
221
|
+
it 'reserves open jobs' do
|
222
222
|
job = create_job
|
223
223
|
expect(described_class.reserve(worker)).to eq(job)
|
224
224
|
end
|
225
225
|
|
226
|
-
it
|
226
|
+
it 'reserves expired jobs' do
|
227
227
|
job = create_job(:locked_by => 'some other worker', :locked_at => described_class.db_time_now - Delayed::Worker.max_run_time - 1.minute)
|
228
228
|
expect(described_class.reserve(worker)).to eq(job)
|
229
229
|
end
|
230
230
|
|
231
|
-
it
|
231
|
+
it 'reserves own jobs' do
|
232
232
|
job = create_job(:locked_by => worker.name, :locked_at => (described_class.db_time_now - 1.minutes))
|
233
233
|
expect(described_class.reserve(worker)).to eq(job)
|
234
234
|
end
|
235
235
|
end
|
236
236
|
|
237
|
-
context
|
238
|
-
it
|
239
|
-
expect(described_class.create(:payload_object => ErrorJob.new
|
237
|
+
context '#name' do
|
238
|
+
it 'is the class name of the job that was enqueued' do
|
239
|
+
expect(described_class.create(:payload_object => ErrorJob.new).name).to eq('ErrorJob')
|
240
240
|
end
|
241
241
|
|
242
|
-
it
|
242
|
+
it 'is the method that will be called if its a performable method object' do
|
243
243
|
job = described_class.new(:payload_object => NamedJob.new)
|
244
244
|
expect(job.name).to eq('named_job')
|
245
245
|
end
|
246
246
|
|
247
|
-
it
|
248
|
-
job = Story.create(:text =>
|
247
|
+
it 'is the instance method that will be called if its a performable method object' do
|
248
|
+
job = Story.create(:text => '...').delay.save
|
249
249
|
expect(job.name).to eq('Story#save')
|
250
250
|
end
|
251
251
|
|
252
|
-
it
|
253
|
-
job = Story.create(:text =>
|
252
|
+
it 'parses from handler on deserialization error' do
|
253
|
+
job = Story.create(:text => '...').delay.text
|
254
254
|
job.payload_object.object.destroy
|
255
255
|
expect(job.reload.name).to eq('Delayed::PerformableMethod')
|
256
256
|
end
|
257
257
|
end
|
258
258
|
|
259
|
-
context
|
259
|
+
context 'worker prioritization' do
|
260
260
|
after do
|
261
261
|
Delayed::Worker.max_priority = nil
|
262
262
|
Delayed::Worker.min_priority = nil
|
263
263
|
end
|
264
264
|
|
265
|
-
it
|
265
|
+
it 'fetches jobs ordered by priority' do
|
266
266
|
10.times { described_class.enqueue SimpleJob.new, :priority => rand(10) }
|
267
267
|
jobs = []
|
268
268
|
10.times { jobs << described_class.reserve(worker) }
|
@@ -272,10 +272,10 @@ shared_examples_for "a delayed_job backend" do
|
|
272
272
|
end
|
273
273
|
end
|
274
274
|
|
275
|
-
it
|
275
|
+
it 'only finds jobs greater than or equal to min priority' do
|
276
276
|
min = 5
|
277
277
|
Delayed::Worker.min_priority = min
|
278
|
-
[4,5,6].sort_by {|
|
278
|
+
[4, 5, 6].sort_by { |_i| rand }.each { |i| create_job :priority => i }
|
279
279
|
2.times do
|
280
280
|
job = described_class.reserve(worker)
|
281
281
|
expect(job.priority).to be >= min
|
@@ -284,10 +284,10 @@ shared_examples_for "a delayed_job backend" do
|
|
284
284
|
expect(described_class.reserve(worker)).to be_nil
|
285
285
|
end
|
286
286
|
|
287
|
-
it
|
287
|
+
it 'only finds jobs less than or equal to max priority' do
|
288
288
|
max = 5
|
289
289
|
Delayed::Worker.max_priority = max
|
290
|
-
[4,5,6].sort_by {|
|
290
|
+
[4, 5, 6].sort_by { |_i| rand }.each { |i| create_job :priority => i }
|
291
291
|
2.times do
|
292
292
|
job = described_class.reserve(worker)
|
293
293
|
expect(job.priority).to be <= max
|
@@ -297,73 +297,73 @@ shared_examples_for "a delayed_job backend" do
|
|
297
297
|
end
|
298
298
|
end
|
299
299
|
|
300
|
-
context
|
300
|
+
context 'clear_locks!' do
|
301
301
|
before do
|
302
302
|
@job = create_job(:locked_by => 'worker1', :locked_at => described_class.db_time_now)
|
303
303
|
end
|
304
304
|
|
305
|
-
it
|
305
|
+
it 'clears locks for the given worker' do
|
306
306
|
described_class.clear_locks!('worker1')
|
307
307
|
expect(described_class.reserve(worker)).to eq(@job)
|
308
308
|
end
|
309
309
|
|
310
|
-
it
|
310
|
+
it 'does not clear locks for other workers' do
|
311
311
|
described_class.clear_locks!('different_worker')
|
312
312
|
expect(described_class.reserve(worker)).not_to eq(@job)
|
313
313
|
end
|
314
314
|
end
|
315
315
|
|
316
|
-
context
|
316
|
+
context 'unlock' do
|
317
317
|
before do
|
318
318
|
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now)
|
319
319
|
end
|
320
320
|
|
321
|
-
it
|
321
|
+
it 'clears locks' do
|
322
322
|
@job.unlock
|
323
323
|
expect(@job.locked_by).to be_nil
|
324
324
|
expect(@job.locked_at).to be_nil
|
325
325
|
end
|
326
326
|
end
|
327
327
|
|
328
|
-
context
|
328
|
+
context 'large handler' do
|
329
329
|
before do
|
330
|
-
text =
|
330
|
+
text = 'Lorem ipsum dolor sit amet. ' * 1000
|
331
331
|
@job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {})
|
332
332
|
end
|
333
333
|
|
334
|
-
it
|
334
|
+
it 'has an id' do
|
335
335
|
expect(@job.id).not_to be_nil
|
336
336
|
end
|
337
337
|
end
|
338
338
|
|
339
|
-
context
|
340
|
-
context
|
339
|
+
context 'named queues' do
|
340
|
+
context 'when worker has one queue set' do
|
341
341
|
before(:each) do
|
342
342
|
worker.queues = ['large']
|
343
343
|
end
|
344
344
|
|
345
|
-
it
|
345
|
+
it 'only works off jobs which are from its queue' do
|
346
346
|
expect(SimpleJob.runs).to eq(0)
|
347
347
|
|
348
|
-
create_job(:queue =>
|
349
|
-
create_job(:queue =>
|
348
|
+
create_job(:queue => 'large')
|
349
|
+
create_job(:queue => 'small')
|
350
350
|
worker.work_off
|
351
351
|
|
352
352
|
expect(SimpleJob.runs).to eq(1)
|
353
353
|
end
|
354
354
|
end
|
355
355
|
|
356
|
-
context
|
356
|
+
context 'when worker has two queue set' do
|
357
357
|
before(:each) do
|
358
|
-
worker.queues = [
|
358
|
+
worker.queues = %w[large small]
|
359
359
|
end
|
360
360
|
|
361
|
-
it
|
361
|
+
it 'only works off jobs which are from its queue' do
|
362
362
|
expect(SimpleJob.runs).to eq(0)
|
363
363
|
|
364
|
-
create_job(:queue =>
|
365
|
-
create_job(:queue =>
|
366
|
-
create_job(:queue =>
|
364
|
+
create_job(:queue => 'large')
|
365
|
+
create_job(:queue => 'small')
|
366
|
+
create_job(:queue => 'medium')
|
367
367
|
create_job
|
368
368
|
worker.work_off
|
369
369
|
|
@@ -371,16 +371,16 @@ shared_examples_for "a delayed_job backend" do
|
|
371
371
|
end
|
372
372
|
end
|
373
373
|
|
374
|
-
context
|
374
|
+
context 'when worker does not have queue set' do
|
375
375
|
before(:each) do
|
376
376
|
worker.queues = []
|
377
377
|
end
|
378
378
|
|
379
|
-
it
|
379
|
+
it 'works off all jobs' do
|
380
380
|
expect(SimpleJob.runs).to eq(0)
|
381
381
|
|
382
|
-
create_job(:queue =>
|
383
|
-
create_job(:queue =>
|
382
|
+
create_job(:queue => 'one')
|
383
|
+
create_job(:queue => 'two')
|
384
384
|
create_job
|
385
385
|
worker.work_off
|
386
386
|
|
@@ -389,84 +389,78 @@ shared_examples_for "a delayed_job backend" do
|
|
389
389
|
end
|
390
390
|
end
|
391
391
|
|
392
|
-
context
|
392
|
+
context 'max_attempts' do
|
393
393
|
before(:each) do
|
394
394
|
@job = described_class.enqueue SimpleJob.new
|
395
395
|
end
|
396
396
|
|
397
|
-
it
|
397
|
+
it 'is not defined' do
|
398
398
|
expect(@job.max_attempts).to be_nil
|
399
399
|
end
|
400
400
|
|
401
|
-
it
|
401
|
+
it 'uses the max_retries value on the payload when defined' do
|
402
402
|
expect(@job.payload_object).to receive(:max_attempts).and_return(99)
|
403
403
|
expect(@job.max_attempts).to eq(99)
|
404
404
|
end
|
405
405
|
end
|
406
406
|
|
407
|
-
describe
|
408
|
-
context
|
409
|
-
it
|
407
|
+
describe 'yaml serialization' do
|
408
|
+
context 'when serializing jobs' do
|
409
|
+
it 'raises error ArgumentError for new records' do
|
410
410
|
story = Story.new(:text => 'hello')
|
411
411
|
if story.respond_to?(:new_record?)
|
412
|
-
expect {
|
413
|
-
story.delay.tell
|
414
|
-
}.to raise_error(ArgumentError, 'Jobs cannot be created for non-persisted records')
|
412
|
+
expect { story.delay.tell }.to raise_error(ArgumentError, 'Jobs cannot be created for non-persisted records')
|
415
413
|
end
|
416
414
|
end
|
417
415
|
|
418
|
-
it
|
416
|
+
it 'raises error ArgumentError for destroyed records' do
|
419
417
|
story = Story.create(:text => 'hello')
|
420
418
|
story.destroy
|
421
|
-
expect {
|
422
|
-
story.delay.tell
|
423
|
-
}.to raise_error(ArgumentError, 'Jobs cannot be created for non-persisted records')
|
419
|
+
expect { story.delay.tell }.to raise_error(ArgumentError, 'Jobs cannot be created for non-persisted records')
|
424
420
|
end
|
425
421
|
end
|
426
422
|
|
427
|
-
context
|
428
|
-
it
|
423
|
+
context 'when reload jobs back' do
|
424
|
+
it 'reloads changed attributes' do
|
429
425
|
story = Story.create(:text => 'hello')
|
430
426
|
job = story.delay.tell
|
431
427
|
story.update_attributes :text => 'goodbye'
|
432
428
|
expect(job.reload.payload_object.object.text).to eq('goodbye')
|
433
429
|
end
|
434
430
|
|
435
|
-
it
|
431
|
+
it 'raises deserialization error for destroyed records' do
|
436
432
|
story = Story.create(:text => 'hello')
|
437
433
|
job = story.delay.tell
|
438
434
|
story.destroy
|
439
|
-
expect {
|
440
|
-
job.reload.payload_object
|
441
|
-
}.to raise_error(Delayed::DeserializationError)
|
435
|
+
expect { job.reload.payload_object }.to raise_error(Delayed::DeserializationError)
|
442
436
|
end
|
443
437
|
end
|
444
438
|
end
|
445
439
|
|
446
|
-
describe
|
440
|
+
describe 'worker integration' do
|
447
441
|
before do
|
448
442
|
Delayed::Job.delete_all
|
449
443
|
SimpleJob.runs = 0
|
450
444
|
end
|
451
445
|
|
452
|
-
describe
|
453
|
-
it
|
446
|
+
describe 'running a job' do
|
447
|
+
it 'fails after Worker.max_run_time' do
|
454
448
|
Delayed::Worker.max_run_time = 1.second
|
455
449
|
job = Delayed::Job.create :payload_object => LongRunningJob.new
|
456
450
|
worker.run(job)
|
457
451
|
expect(job.reload.last_error).to match(/expired/)
|
458
|
-
expect(job.reload.last_error).to match(/Delayed::Worker
|
452
|
+
expect(job.reload.last_error).to match(/Delayed::Worker\.max_run_time is only 1 second/)
|
459
453
|
expect(job.attempts).to eq(1)
|
460
454
|
end
|
461
455
|
|
462
|
-
context
|
456
|
+
context 'when the job raises a deserialization error' do
|
463
457
|
after do
|
464
458
|
Delayed::Worker.destroy_failed_jobs = true
|
465
459
|
end
|
466
460
|
|
467
|
-
it
|
461
|
+
it 'marks the job as failed' do
|
468
462
|
Delayed::Worker.destroy_failed_jobs = false
|
469
|
-
job = described_class.create! :handler =>
|
463
|
+
job = described_class.create! :handler => '--- !ruby/object:JobThatDoesNotExist {}'
|
470
464
|
worker.work_off
|
471
465
|
job.reload
|
472
466
|
expect(job).to be_failed
|
@@ -474,7 +468,7 @@ shared_examples_for "a delayed_job backend" do
|
|
474
468
|
end
|
475
469
|
end
|
476
470
|
|
477
|
-
describe
|
471
|
+
describe 'failed jobs' do
|
478
472
|
before do
|
479
473
|
@job = Delayed::Job.enqueue(ErrorJob.new, :run_at => described_class.db_time_now - 1)
|
480
474
|
end
|
@@ -484,7 +478,7 @@ shared_examples_for "a delayed_job backend" do
|
|
484
478
|
Delayed::Worker.destroy_failed_jobs = true
|
485
479
|
end
|
486
480
|
|
487
|
-
it
|
481
|
+
it 'records last_error when destroy_failed_jobs = false, max_attempts = 1' do
|
488
482
|
Delayed::Worker.destroy_failed_jobs = false
|
489
483
|
Delayed::Worker.max_attempts = 1
|
490
484
|
worker.run(@job)
|
@@ -494,7 +488,7 @@ shared_examples_for "a delayed_job backend" do
|
|
494
488
|
expect(@job).to be_failed
|
495
489
|
end
|
496
490
|
|
497
|
-
it
|
491
|
+
it 're-schedules jobs after failing' do
|
498
492
|
worker.work_off
|
499
493
|
@job.reload
|
500
494
|
expect(@job.last_error).to match(/did not work/)
|
@@ -506,7 +500,7 @@ shared_examples_for "a delayed_job backend" do
|
|
506
500
|
expect(@job.locked_at).to be_nil
|
507
501
|
end
|
508
502
|
|
509
|
-
it
|
503
|
+
it 're-schedules jobs with handler provided time if present' do
|
510
504
|
job = Delayed::Job.enqueue(CustomRescheduleJob.new(99.minutes))
|
511
505
|
worker.run(job)
|
512
506
|
job.reload
|
@@ -518,26 +512,33 @@ shared_examples_for "a delayed_job backend" do
|
|
518
512
|
error_with_nil_message = StandardError.new
|
519
513
|
expect(error_with_nil_message).to receive(:message).twice.and_return(nil)
|
520
514
|
expect(@job).to receive(:invoke_job).and_raise error_with_nil_message
|
521
|
-
expect{worker.run(@job)}.not_to raise_error
|
515
|
+
expect { worker.run(@job) }.not_to raise_error
|
522
516
|
end
|
523
517
|
end
|
524
518
|
|
525
|
-
context
|
519
|
+
context 'reschedule' do
|
526
520
|
before do
|
527
521
|
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
528
522
|
end
|
529
523
|
|
530
|
-
shared_examples_for
|
524
|
+
shared_examples_for 'any failure more than Worker.max_attempts times' do
|
531
525
|
context "when the job's payload has a #failure hook" do
|
532
526
|
before do
|
533
527
|
@job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
|
534
528
|
expect(@job.payload_object).to respond_to(:failure)
|
535
529
|
end
|
536
530
|
|
537
|
-
it
|
531
|
+
it 'runs that hook' do
|
538
532
|
expect(@job.payload_object).to receive(:failure)
|
539
533
|
worker.reschedule(@job)
|
540
534
|
end
|
535
|
+
|
536
|
+
it 'handles error in hook' do
|
537
|
+
Delayed::Worker.destroy_failed_jobs = false
|
538
|
+
@job.payload_object.raise_error = true
|
539
|
+
expect { worker.reschedule(@job) }.not_to raise_error
|
540
|
+
expect(@job.failed_at).to_not be_nil
|
541
|
+
end
|
541
542
|
end
|
542
543
|
|
543
544
|
context "when the job's payload has no #failure hook" do
|
@@ -555,23 +556,23 @@ shared_examples_for "a delayed_job backend" do
|
|
555
556
|
expect(@job.payload_object).not_to respond_to(:failure)
|
556
557
|
end
|
557
558
|
|
558
|
-
it
|
559
|
-
expect
|
559
|
+
it 'does not try to run that hook' do
|
560
|
+
expect do
|
560
561
|
Delayed::Worker.max_attempts.times { worker.reschedule(@job) }
|
561
|
-
|
562
|
+
end.not_to raise_exception
|
562
563
|
end
|
563
564
|
end
|
564
565
|
end
|
565
566
|
|
566
|
-
context
|
567
|
-
|
567
|
+
context 'and we want to destroy jobs' do
|
568
|
+
it_behaves_like 'any failure more than Worker.max_attempts times'
|
568
569
|
|
569
|
-
it
|
570
|
+
it 'is destroyed if it failed more than Worker.max_attempts times' do
|
570
571
|
expect(@job).to receive(:destroy)
|
571
572
|
Delayed::Worker.max_attempts.times { worker.reschedule(@job) }
|
572
573
|
end
|
573
574
|
|
574
|
-
it
|
575
|
+
it 'is not destroyed if failed fewer than Worker.max_attempts times' do
|
575
576
|
expect(@job).not_to receive(:destroy)
|
576
577
|
(Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) }
|
577
578
|
end
|
@@ -586,15 +587,15 @@ shared_examples_for "a delayed_job backend" do
|
|
586
587
|
Delayed::Worker.destroy_failed_jobs = true
|
587
588
|
end
|
588
589
|
|
589
|
-
|
590
|
+
it_behaves_like 'any failure more than Worker.max_attempts times'
|
590
591
|
|
591
|
-
it
|
592
|
+
it 'is failed if it failed more than Worker.max_attempts times' do
|
592
593
|
expect(@job.reload).not_to be_failed
|
593
594
|
Delayed::Worker.max_attempts.times { worker.reschedule(@job) }
|
594
595
|
expect(@job.reload).to be_failed
|
595
596
|
end
|
596
597
|
|
597
|
-
it
|
598
|
+
it 'is not failed if it failed fewer than Worker.max_attempts times' do
|
598
599
|
(Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) }
|
599
600
|
expect(@job.reload).not_to be_failed
|
600
601
|
end
|