delayed_job_on_steroids 1.7.5 → 2.0.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/.gitignore +1 -0
- data/README.markdown +124 -0
- data/Rakefile +5 -6
- data/VERSION +1 -1
- data/delayed_job_on_steroids.gemspec +18 -12
- data/generators/delayed_job/delayed_job_generator.rb +9 -0
- data/generators/delayed_job/templates/script +5 -0
- data/generators/delayed_job_migration/templates/migration.rb +18 -14
- data/init.rb +1 -1
- data/lib/delayed_job_on_steroids.rb +10 -0
- data/lib/delayed_on_steroids/command.rb +104 -0
- data/lib/{delayed → delayed_on_steroids}/job.rb +91 -102
- data/lib/delayed_on_steroids/job_deprecations.rb +19 -0
- data/lib/{delayed → delayed_on_steroids}/message_sending.rb +5 -1
- data/lib/{delayed → delayed_on_steroids}/performable_method.rb +0 -0
- data/lib/delayed_on_steroids/tasks.rb +28 -0
- data/lib/delayed_on_steroids/worker.rb +56 -0
- data/spec/database.rb +18 -9
- data/spec/delayed_method_spec.rb +27 -2
- data/spec/job_spec.rb +133 -82
- data/spec/worker_spec.rb +15 -0
- data/tasks/jobs.rake +1 -1
- metadata +19 -13
- data/README.textile +0 -129
- data/lib/delayed/worker.rb +0 -55
- data/lib/delayed_job.rb +0 -13
- data/tasks/tasks.rb +0 -16
@@ -0,0 +1,19 @@
|
|
1
|
+
module Delayed
|
2
|
+
module JobDeprecations
|
3
|
+
def self.move_methods_to_worker(*old_methods)
|
4
|
+
old_methods.each do |old_method|
|
5
|
+
new_method = old_method.to_s.gsub("worker_", "").to_sym
|
6
|
+
define_method(old_method) do |*args|
|
7
|
+
warn "#{caller[0]}: Job's #{old_method} is deprecated. Use Worker.#{new_method} instead."
|
8
|
+
Worker.send(new_method, *args)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
move_methods_to_worker :min_priority, :min_priority=
|
14
|
+
move_methods_to_worker :max_priority, :max_priority=
|
15
|
+
move_methods_to_worker :job_types, :job_types=
|
16
|
+
move_methods_to_worker :destroy_failed_jobs, :destroy_failed_jobs=
|
17
|
+
move_methods_to_worker :worker_name, :worker_name=
|
18
|
+
end
|
19
|
+
end
|
@@ -1,7 +1,11 @@
|
|
1
1
|
module Delayed
|
2
2
|
module MessageSending
|
3
3
|
def send_later(method, *args)
|
4
|
-
Delayed::Job.enqueue
|
4
|
+
Delayed::Job.enqueue(Delayed::PerformableMethod.new(self, method.to_sym, args))
|
5
|
+
end
|
6
|
+
|
7
|
+
def send_at(time, method, *args)
|
8
|
+
Delayed::Job.enqueue(Delayed::PerformableMethod.new(self, method.to_sym, args), 0, time)
|
5
9
|
end
|
6
10
|
|
7
11
|
module ClassMethods
|
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# Re-definitions are appended to existing tasks
|
2
|
+
task :environment
|
3
|
+
|
4
|
+
namespace :jobs do
|
5
|
+
|
6
|
+
namespace :clear do
|
7
|
+
desc "Remove failed jobs from delayed_job queue"
|
8
|
+
task :failed => [:environment] do
|
9
|
+
Delayed::Job.delete_all("failed_at IS NOT NULL")
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Clear entire delayed_job queue"
|
13
|
+
task :all => [:environment] do
|
14
|
+
Delayed::Job.delete_all
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# Deprecated synonim
|
19
|
+
task :clear => ['clear:all']
|
20
|
+
|
21
|
+
desc "Report delayed_job statistics"
|
22
|
+
task :stats => [:environment] do
|
23
|
+
jobs = Delayed::Job.all
|
24
|
+
puts "Active jobs : #{ jobs.count{ |job| job.locked? } }"
|
25
|
+
puts "Scheduled jobs : #{ jobs.count{ |job| not (job.locked? or job.failed?) } }"
|
26
|
+
puts "Failed stored jobs : #{ jobs.count{ |job| job.failed? } }"
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Delayed
|
2
|
+
class Worker
|
3
|
+
include Singleton
|
4
|
+
|
5
|
+
SLEEP = 5
|
6
|
+
|
7
|
+
cattr_accessor :logger
|
8
|
+
@@logger = Rails.logger if defined?(Rails)
|
9
|
+
|
10
|
+
cattr_accessor :min_priority, :max_priority
|
11
|
+
cattr_accessor :job_types
|
12
|
+
|
13
|
+
# Every worker should has a unique name. If you start worker via +delayed_job+ script, name will be generated
|
14
|
+
# automatically based on the hostname and the pid of the process.
|
15
|
+
# There is advantage to assign name manually:
|
16
|
+
# workers can safely resume working on tasks which are locked by themselves (the worker will assume that it crashed before).
|
17
|
+
cattr_accessor :name
|
18
|
+
|
19
|
+
# By default failed jobs are destroyed after too many attempts.
|
20
|
+
# If you want to keep them around (perhaps to inspect the reason for the failure), set this to false.
|
21
|
+
cattr_accessor :destroy_failed_jobs
|
22
|
+
@@destroy_failed_jobs = true
|
23
|
+
|
24
|
+
# Starts worker
|
25
|
+
def start
|
26
|
+
@@logger.info("* [#{@@name}] Starting job worker...")
|
27
|
+
|
28
|
+
trap('TERM') { @@logger.info("* [#{@@name}] Exiting..."); $exit = true }
|
29
|
+
trap('INT') { @@logger.info("* [#{@@name}] Exiting..."); $exit = true }
|
30
|
+
|
31
|
+
loop do
|
32
|
+
result = nil
|
33
|
+
|
34
|
+
realtime = Benchmark.realtime do
|
35
|
+
result = Delayed::Job.work_off
|
36
|
+
end
|
37
|
+
|
38
|
+
count = result.sum
|
39
|
+
|
40
|
+
break if $exit
|
41
|
+
|
42
|
+
if count.zero?
|
43
|
+
sleep(SLEEP)
|
44
|
+
else
|
45
|
+
@@logger.info("* [#{@@name}] #{count} jobs processed at %.4f j/s, %d failed..." % [count / realtime, result.last])
|
46
|
+
end
|
47
|
+
|
48
|
+
break if $exit
|
49
|
+
end
|
50
|
+
|
51
|
+
ensure
|
52
|
+
# When a worker is exiting, make sure we don't have any locked jobs.
|
53
|
+
Delayed::Job.update_all("locked_by = null, locked_at = null", ["locked_by = ?", @@name])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/spec/database.rb
CHANGED
@@ -7,26 +7,35 @@ gem 'sqlite3-ruby'
|
|
7
7
|
|
8
8
|
require File.dirname(__FILE__) + '/../init'
|
9
9
|
require 'spec'
|
10
|
-
|
11
|
-
|
10
|
+
|
11
|
+
Delayed::Worker.logger = Logger.new('/tmp/dj.log')
|
12
12
|
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => '/tmp/jobs.sqlite')
|
13
13
|
ActiveRecord::Migration.verbose = false
|
14
14
|
|
15
15
|
ActiveRecord::Schema.define do
|
16
16
|
|
17
17
|
create_table :delayed_jobs, :force => true do |table|
|
18
|
-
table.integer :priority, :default => 0
|
19
|
-
table.integer :attempts, :default => 0
|
20
|
-
table.text :handler
|
21
|
-
table.string :job_type
|
18
|
+
table.integer :priority, :null => false, :default => 0
|
19
|
+
table.integer :attempts, :null => false, :default => 0
|
20
|
+
table.text :handler, :null => false
|
21
|
+
table.string :job_type, :null => false
|
22
|
+
table.string :job_tag
|
22
23
|
table.string :last_error
|
23
|
-
table.datetime :run_at
|
24
|
+
table.datetime :run_at, :null => false
|
24
25
|
table.datetime :locked_at
|
25
26
|
table.string :locked_by
|
26
27
|
table.datetime :failed_at
|
27
28
|
table.timestamps
|
28
29
|
end
|
29
30
|
|
31
|
+
add_index :delayed_jobs, [:priority, :run_at]
|
32
|
+
add_index :delayed_jobs, :job_type
|
33
|
+
add_index :delayed_jobs, :job_tag
|
34
|
+
add_index :delayed_jobs, :run_at
|
35
|
+
add_index :delayed_jobs, :locked_at
|
36
|
+
add_index :delayed_jobs, :locked_by
|
37
|
+
add_index :delayed_jobs, :failed_at
|
38
|
+
|
30
39
|
create_table :stories, :force => true do |table|
|
31
40
|
table.string :text
|
32
41
|
end
|
@@ -36,8 +45,8 @@ end
|
|
36
45
|
|
37
46
|
# Purely useful for test cases...
|
38
47
|
class Story < ActiveRecord::Base
|
39
|
-
def tell; text; end
|
48
|
+
def tell; text; end
|
40
49
|
def whatever(n, _); tell*n; end
|
41
|
-
|
50
|
+
|
42
51
|
handle_asynchronously :whatever
|
43
52
|
end
|
data/spec/delayed_method_spec.rb
CHANGED
@@ -37,7 +37,10 @@ class StoryReader
|
|
37
37
|
end
|
38
38
|
|
39
39
|
describe 'random ruby objects' do
|
40
|
-
before
|
40
|
+
before do
|
41
|
+
Delayed::Worker.name = 'worker'
|
42
|
+
Delayed::Job.delete_all
|
43
|
+
end
|
41
44
|
|
42
45
|
it "should respond_to :send_later method" do
|
43
46
|
|
@@ -78,7 +81,7 @@ describe 'random ruby objects' do
|
|
78
81
|
|
79
82
|
Delayed::Job.count.should == 1
|
80
83
|
|
81
|
-
Delayed::Job.reserve_and_run_one_job
|
84
|
+
Delayed::Job.reserve_and_run_one_job.should == true
|
82
85
|
|
83
86
|
Delayed::Job.count.should == 0
|
84
87
|
|
@@ -125,4 +128,26 @@ describe 'random ruby objects' do
|
|
125
128
|
job.payload_object.perform.should == 'Once upon...'
|
126
129
|
end
|
127
130
|
|
131
|
+
context "send_at" do
|
132
|
+
it "should queue a new job" do
|
133
|
+
lambda do
|
134
|
+
"string".send_at(1.hour.from_now, :length)
|
135
|
+
end.should change { Delayed::Job.count }.by(1)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should schedule the job in the future" do
|
139
|
+
time = 1.hour.from_now
|
140
|
+
job = "string".send_at(time, :length)
|
141
|
+
job.run_at.should == time
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should store payload as PerformableMethod" do
|
145
|
+
job = "string".send_at(1.hour.from_now, :count, 'r')
|
146
|
+
job.payload_object.class.should == Delayed::PerformableMethod
|
147
|
+
job.payload_object.method.should == :count
|
148
|
+
job.payload_object.args.should == ['r']
|
149
|
+
job.payload_object.perform.should == 1
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
128
153
|
end
|
data/spec/job_spec.rb
CHANGED
@@ -8,28 +8,38 @@ end
|
|
8
8
|
class ErrorJob
|
9
9
|
cattr_accessor :runs; self.runs = 0
|
10
10
|
def perform; raise 'did not work'; end
|
11
|
-
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class LongRunningJob
|
14
|
+
def perform; sleep 250; end
|
15
|
+
end
|
12
16
|
|
13
17
|
module M
|
14
18
|
class ModuleJob
|
15
19
|
cattr_accessor :runs; self.runs = 0
|
16
|
-
def perform; @@runs += 1; end
|
20
|
+
def perform; @@runs += 1; end
|
17
21
|
end
|
18
|
-
|
19
22
|
end
|
20
23
|
|
21
24
|
describe Delayed::Job do
|
22
|
-
before do
|
23
|
-
Delayed::
|
24
|
-
Delayed::
|
25
|
-
|
25
|
+
before do
|
26
|
+
Delayed::Worker.max_priority = nil
|
27
|
+
Delayed::Worker.min_priority = nil
|
28
|
+
|
26
29
|
Delayed::Job.delete_all
|
27
30
|
end
|
28
|
-
|
31
|
+
|
29
32
|
before(:each) do
|
30
33
|
SimpleJob.runs = 0
|
31
34
|
end
|
32
35
|
|
36
|
+
it "responds to deprecated methods moved to Worker" do
|
37
|
+
Delayed::JobDeprecations.instance_methods.each do |m|
|
38
|
+
Delayed::Job.respond_to?(m).should == true
|
39
|
+
silence_warnings { Delayed::Job.send(m) } unless m.to_s =~ /=$/
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
33
43
|
it "should set run_at automatically if not set" do
|
34
44
|
Delayed::Job.create(:payload_object => ErrorJob.new ).run_at.should_not == nil
|
35
45
|
end
|
@@ -73,25 +83,29 @@ describe Delayed::Job do
|
|
73
83
|
it "should work on specified job types" do
|
74
84
|
SimpleJob.runs.should == 0
|
75
85
|
|
76
|
-
Delayed::
|
86
|
+
Delayed::Worker.job_types = "SimpleJob"
|
77
87
|
Delayed::Job.enqueue SimpleJob.new
|
78
88
|
Delayed::Job.work_off
|
79
89
|
|
80
90
|
SimpleJob.runs.should == 1
|
81
91
|
|
82
|
-
Delayed::
|
92
|
+
Delayed::Worker.job_types = nil
|
83
93
|
end
|
84
94
|
|
85
95
|
it "should not work on unspecified job types" do
|
86
96
|
SimpleJob.runs.should == 0
|
87
97
|
|
88
|
-
Delayed::
|
98
|
+
Delayed::Worker.job_types = "AnotherJob"
|
89
99
|
Delayed::Job.enqueue SimpleJob.new
|
90
100
|
Delayed::Job.work_off
|
91
101
|
|
92
102
|
SimpleJob.runs.should == 0
|
93
103
|
|
94
|
-
Delayed::
|
104
|
+
Delayed::Worker.job_types = nil
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should allow to specify job tag" do
|
108
|
+
Delayed::Job.create(:payload_object => ErrorJob.new, :job_tag => 'my tag' ).job_tag.should == 'my tag'
|
95
109
|
end
|
96
110
|
|
97
111
|
it "should work with eval jobs" do
|
@@ -106,7 +120,7 @@ describe Delayed::Job do
|
|
106
120
|
|
107
121
|
$eval_job_ran.should == true
|
108
122
|
end
|
109
|
-
|
123
|
+
|
110
124
|
it "should work with jobs in modules" do
|
111
125
|
M::ModuleJob.runs.should == 0
|
112
126
|
|
@@ -115,7 +129,7 @@ describe Delayed::Job do
|
|
115
129
|
|
116
130
|
M::ModuleJob.runs.should == 1
|
117
131
|
end
|
118
|
-
|
132
|
+
|
119
133
|
it "should re-schedule by about 1 second at first and increment this more and more minutes when it fails to execute properly" do
|
120
134
|
Delayed::Job.enqueue ErrorJob.new
|
121
135
|
Delayed::Job.work_off(1)
|
@@ -170,28 +184,52 @@ describe Delayed::Job do
|
|
170
184
|
job.should_receive(:attempt_to_load).with('Delayed::JobThatDoesNotExist').and_return(true)
|
171
185
|
lambda { job.payload_object.perform }.should raise_error(Delayed::DeserializationError)
|
172
186
|
end
|
173
|
-
|
174
|
-
it "should be failed if it failed more than MAX_ATTEMPTS times and we don't want to destroy jobs" do
|
175
|
-
default = Delayed::Job.destroy_failed_jobs
|
176
|
-
Delayed::Job.destroy_failed_jobs = false
|
177
187
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
188
|
+
context "reschedule" do
|
189
|
+
before do
|
190
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
191
|
+
end
|
182
192
|
|
183
|
-
|
184
|
-
|
193
|
+
context "and we want to destroy jobs" do
|
194
|
+
before do
|
195
|
+
Delayed::Worker.destroy_failed_jobs = true
|
196
|
+
end
|
185
197
|
|
186
|
-
|
187
|
-
|
188
|
-
|
198
|
+
it "should be destroyed if it failed more than MAX_ATTEMPTS times" do
|
199
|
+
@job.should_receive(:destroy)
|
200
|
+
Delayed::Job::MAX_ATTEMPTS.times { @job.reschedule 'FAIL' }
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should not be destroyed if failed fewer than MAX_ATTEMPTS times" do
|
204
|
+
@job.should_not_receive(:destroy)
|
205
|
+
(Delayed::Job::MAX_ATTEMPTS - 1).times { @job.reschedule 'FAIL' }
|
206
|
+
end
|
207
|
+
end
|
189
208
|
|
190
|
-
|
191
|
-
|
192
|
-
|
209
|
+
context "and we don't want to destroy jobs" do
|
210
|
+
before do
|
211
|
+
Delayed::Worker.destroy_failed_jobs = false
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should be failed if it failed more than MAX_ATTEMPTS times" do
|
215
|
+
@job.reload.failed_at.should == nil
|
216
|
+
Delayed::Job::MAX_ATTEMPTS.times { @job.reschedule 'FAIL' }
|
217
|
+
@job.reload.failed?.should == true
|
218
|
+
end
|
193
219
|
|
194
|
-
|
220
|
+
it "should not be failed if it failed fewer than MAX_ATTEMPTS times" do
|
221
|
+
(Delayed::Job::MAX_ATTEMPTS - 1).times { @job.reschedule 'FAIL' }
|
222
|
+
@job.reload.failed?.should == false
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should fail after MAX_RUN_TIME" do
|
229
|
+
@job = Delayed::Job.create :payload_object => LongRunningJob.new
|
230
|
+
Delayed::Job.reserve_and_run_one_job(1.second)
|
231
|
+
@job.reload.last_error.should =~ /expired/
|
232
|
+
@job.attempts.should == 1
|
195
233
|
end
|
196
234
|
|
197
235
|
it "should never find failed jobs" do
|
@@ -202,7 +240,7 @@ describe Delayed::Job do
|
|
202
240
|
context "when another worker is already performing an task, it" do
|
203
241
|
|
204
242
|
before :each do
|
205
|
-
Delayed::
|
243
|
+
Delayed::Worker.name = 'worker1'
|
206
244
|
@job = Delayed::Job.create :payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => Delayed::Job.db_time_now - 5.minutes
|
207
245
|
end
|
208
246
|
|
@@ -212,26 +250,27 @@ describe Delayed::Job do
|
|
212
250
|
|
213
251
|
it "should allow a second worker to get exclusive access if the timeout has passed" do
|
214
252
|
@job.lock_exclusively!(1.minute, 'worker2').should == true
|
215
|
-
end
|
216
|
-
|
253
|
+
end
|
254
|
+
|
217
255
|
it "should be able to get access to the task if it was started more then MAX_RUN_TIME ago" do
|
218
256
|
@job.locked_at = Delayed::Job.db_time_now - 5.hours
|
219
257
|
@job.save
|
220
258
|
|
221
259
|
@job.lock_exclusively!(4.hours, 'worker2').should == true
|
222
260
|
@job.reload
|
261
|
+
@job.locked?.should == true
|
223
262
|
@job.locked_by.should == 'worker2'
|
224
263
|
@job.locked_at.should > 1.minute.ago
|
225
264
|
end
|
226
265
|
|
227
266
|
it "should not be found by another worker" do
|
228
|
-
Delayed::
|
267
|
+
Delayed::Worker.name = 'worker2'
|
229
268
|
|
230
269
|
Delayed::Job.find_available(1, 6.minutes).length.should == 0
|
231
270
|
end
|
232
271
|
|
233
272
|
it "should be found by another worker if the time has expired" do
|
234
|
-
Delayed::
|
273
|
+
Delayed::Worker.name = 'worker2'
|
235
274
|
|
236
275
|
Delayed::Job.find_available(1, 4.minutes).length.should == 1
|
237
276
|
end
|
@@ -240,52 +279,71 @@ describe Delayed::Job do
|
|
240
279
|
@job.lock_exclusively! 5.minutes, 'worker1'
|
241
280
|
@job.lock_exclusively! 5.minutes, 'worker1'
|
242
281
|
@job.lock_exclusively! 5.minutes, 'worker1'
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "when another worker has worked on a task since the job was found to be available, it" do
|
286
|
+
|
287
|
+
before :each do
|
288
|
+
Delayed::Worker.name = 'worker1'
|
289
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
290
|
+
@job_copy_for_worker_2 = Delayed::Job.find(@job.id)
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
|
294
|
+
@job.delete
|
295
|
+
@job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
|
296
|
+
end
|
297
|
+
|
298
|
+
it "should not allow a second worker to get exclusive access if failed to be processed by worker1 and run_at time is now in future (due to backing off behaviour)" do
|
299
|
+
@job.update_attributes(:attempts => 1, :run_at => Delayed::Job.db_time_now + 1.day)
|
300
|
+
@job_copy_for_worker_2.lock_exclusively!(4.hours, 'worker2').should == false
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
246
304
|
context "#name" do
|
247
305
|
it "should be the class name of the job that was enqueued" do
|
248
306
|
Delayed::Job.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
|
249
307
|
end
|
250
308
|
|
251
309
|
it "should be the method that will be called if its a performable method object" do
|
252
|
-
Delayed::Job.send_later(:
|
253
|
-
Delayed::Job.last.name.should == 'Delayed::Job.
|
310
|
+
Delayed::Job.send_later(:db_time_now)
|
311
|
+
Delayed::Job.last.name.should == 'Delayed::Job.db_time_now'
|
254
312
|
|
255
313
|
end
|
256
314
|
it "should be the instance method that will be called if its a performable method object" do
|
257
|
-
story = Story.create :text => "..."
|
258
|
-
|
315
|
+
story = Story.create :text => "..."
|
316
|
+
|
259
317
|
story.send_later(:save)
|
260
|
-
|
318
|
+
|
261
319
|
Delayed::Job.last.name.should == 'Story#save'
|
262
320
|
end
|
263
321
|
end
|
264
|
-
|
322
|
+
|
265
323
|
context "worker prioritization" do
|
266
|
-
|
324
|
+
|
267
325
|
before(:each) do
|
268
|
-
Delayed::
|
269
|
-
Delayed::
|
326
|
+
Delayed::Worker.max_priority = nil
|
327
|
+
Delayed::Worker.min_priority = nil
|
270
328
|
end
|
271
|
-
|
329
|
+
|
272
330
|
it "should only work_off jobs that are >= min_priority" do
|
273
|
-
Delayed::
|
274
|
-
Delayed::
|
331
|
+
Delayed::Worker.min_priority = -5
|
332
|
+
Delayed::Worker.max_priority = 5
|
275
333
|
SimpleJob.runs.should == 0
|
276
|
-
|
334
|
+
|
277
335
|
Delayed::Job.enqueue SimpleJob.new, -10
|
278
336
|
Delayed::Job.enqueue SimpleJob.new, 0
|
279
337
|
Delayed::Job.work_off
|
280
|
-
|
338
|
+
|
281
339
|
SimpleJob.runs.should == 1
|
282
340
|
end
|
283
|
-
|
341
|
+
|
284
342
|
it "should only work_off jobs that are <= max_priority" do
|
285
|
-
Delayed::
|
286
|
-
Delayed::
|
343
|
+
Delayed::Worker.min_priority = -5
|
344
|
+
Delayed::Worker.max_priority = 5
|
287
345
|
SimpleJob.runs.should == 0
|
288
|
-
|
346
|
+
|
289
347
|
Delayed::Job.enqueue SimpleJob.new, 10
|
290
348
|
Delayed::Job.enqueue SimpleJob.new, 0
|
291
349
|
|
@@ -293,44 +351,37 @@ describe Delayed::Job do
|
|
293
351
|
|
294
352
|
SimpleJob.runs.should == 1
|
295
353
|
end
|
296
|
-
|
354
|
+
|
297
355
|
it "should fetch jobs ordered by priority" do
|
298
356
|
NUM = 10
|
299
357
|
NUM.times { Delayed::Job.enqueue SimpleJob.new, rand(NUM) }
|
300
358
|
jobs = Delayed::Job.find_available(NUM)
|
301
|
-
|
302
|
-
jobs[0..-2].each_index do |i|
|
303
|
-
if (jobs[i].priority < jobs[i+1].priority)
|
304
|
-
ordered = false
|
305
|
-
break
|
306
|
-
end
|
307
|
-
end
|
308
|
-
ordered.should == true
|
359
|
+
jobs[0..-2].each_index { |i| (jobs[i].priority <= jobs[i+1].priority).should == true }
|
309
360
|
end
|
310
|
-
|
361
|
+
|
311
362
|
end
|
312
|
-
|
363
|
+
|
313
364
|
context "when pulling jobs off the queue for processing, it" do
|
314
365
|
before(:each) do
|
315
366
|
@job = Delayed::Job.create(
|
316
|
-
:payload_object => SimpleJob.new,
|
317
|
-
:locked_by => 'worker1',
|
367
|
+
:payload_object => SimpleJob.new,
|
368
|
+
:locked_by => 'worker1',
|
318
369
|
:locked_at => Delayed::Job.db_time_now - 5.minutes)
|
319
370
|
end
|
320
371
|
|
321
372
|
it "should leave the queue in a consistent state and not run the job if locking fails" do
|
322
|
-
SimpleJob.runs.should == 0
|
373
|
+
SimpleJob.runs.should == 0
|
323
374
|
@job.stub!(:lock_exclusively!).with(any_args).once.and_return(false)
|
324
375
|
Delayed::Job.should_receive(:find_available).once.and_return([@job])
|
325
376
|
Delayed::Job.work_off(1)
|
326
377
|
SimpleJob.runs.should == 0
|
327
378
|
end
|
328
|
-
|
379
|
+
|
329
380
|
end
|
330
|
-
|
381
|
+
|
331
382
|
context "while running alongside other workers that locked jobs, it" do
|
332
383
|
before(:each) do
|
333
|
-
Delayed::
|
384
|
+
Delayed::Worker.name = 'worker1'
|
334
385
|
Delayed::Job.create(:payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
335
386
|
Delayed::Job.create(:payload_object => SimpleJob.new, :locked_by => 'worker2', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
336
387
|
Delayed::Job.create(:payload_object => SimpleJob.new)
|
@@ -338,14 +389,14 @@ describe Delayed::Job do
|
|
338
389
|
end
|
339
390
|
|
340
391
|
it "should ingore locked jobs from other workers" do
|
341
|
-
Delayed::
|
392
|
+
Delayed::Worker.name = 'worker3'
|
342
393
|
SimpleJob.runs.should == 0
|
343
394
|
Delayed::Job.work_off
|
344
395
|
SimpleJob.runs.should == 1 # runs the one open job
|
345
396
|
end
|
346
397
|
|
347
398
|
it "should find our own jobs regardless of locks" do
|
348
|
-
Delayed::
|
399
|
+
Delayed::Worker.name = 'worker1'
|
349
400
|
SimpleJob.runs.should == 0
|
350
401
|
Delayed::Job.work_off
|
351
402
|
SimpleJob.runs.should == 3 # runs open job plus worker1 jobs that were already locked
|
@@ -354,7 +405,7 @@ describe Delayed::Job do
|
|
354
405
|
|
355
406
|
context "while running with locked and expired jobs, it" do
|
356
407
|
before(:each) do
|
357
|
-
Delayed::
|
408
|
+
Delayed::Worker.name = 'worker1'
|
358
409
|
exp_time = Delayed::Job.db_time_now - (1.minutes + Delayed::Job::MAX_RUN_TIME)
|
359
410
|
Delayed::Job.create(:payload_object => SimpleJob.new, :locked_by => 'worker1', :locked_at => exp_time)
|
360
411
|
Delayed::Job.create(:payload_object => SimpleJob.new, :locked_by => 'worker2', :locked_at => (Delayed::Job.db_time_now - 1.minutes))
|
@@ -363,14 +414,14 @@ describe Delayed::Job do
|
|
363
414
|
end
|
364
415
|
|
365
416
|
it "should only find unlocked and expired jobs" do
|
366
|
-
Delayed::
|
417
|
+
Delayed::Worker.name = 'worker3'
|
367
418
|
SimpleJob.runs.should == 0
|
368
419
|
Delayed::Job.work_off
|
369
420
|
SimpleJob.runs.should == 2 # runs the one open job and one expired job
|
370
421
|
end
|
371
422
|
|
372
423
|
it "should ignore locks when finding our own jobs" do
|
373
|
-
Delayed::
|
424
|
+
Delayed::Worker.name = 'worker1'
|
374
425
|
SimpleJob.runs.should == 0
|
375
426
|
Delayed::Job.work_off
|
376
427
|
SimpleJob.runs.should == 3 # runs open job plus worker1 jobs
|
@@ -382,9 +433,9 @@ describe Delayed::Job do
|
|
382
433
|
context "db_time_now" do
|
383
434
|
it "should return time in current time zone if set" do
|
384
435
|
Time.zone = 'Eastern Time (US & Canada)'
|
385
|
-
Delayed::Job.db_time_now.zone.should match
|
436
|
+
Delayed::Job.db_time_now.zone.should match(/EST|EDT/)
|
386
437
|
end
|
387
|
-
|
438
|
+
|
388
439
|
it "should return UTC time if that is the AR default" do
|
389
440
|
Time.zone = nil
|
390
441
|
ActiveRecord::Base.default_timezone = :utc
|
@@ -397,5 +448,5 @@ describe Delayed::Job do
|
|
397
448
|
Delayed::Job.db_time_now.zone.should_not == 'UTC'
|
398
449
|
end
|
399
450
|
end
|
400
|
-
|
451
|
+
|
401
452
|
end
|