delayed_job 4.0.2 → 4.0.3
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.
- 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
|