inst-jobs 0.11.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.
- checksums.yaml +7 -0
- data/bin/inst_job +4 -0
- data/db/migrate/20101216224513_create_delayed_jobs.rb +40 -0
- data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +14 -0
- data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +13 -0
- data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +14 -0
- data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +26 -0
- data/db/migrate/20110610213249_optimize_delayed_jobs.rb +40 -0
- data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +52 -0
- data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +31 -0
- data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +15 -0
- data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +80 -0
- data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +15 -0
- data/db/migrate/20120608191051_add_jobs_run_at_index.rb +15 -0
- data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +13 -0
- data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +13 -0
- data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140512213941_add_source_to_jobs.rb +15 -0
- data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +70 -0
- data/db/migrate/20151123210429_add_expires_at_to_jobs.rb +15 -0
- data/db/migrate/20151210162949_improve_max_concurrent.rb +50 -0
- data/lib/delayed/backend/active_record.rb +340 -0
- data/lib/delayed/backend/base.rb +335 -0
- data/lib/delayed/backend/redis/bulk_update.lua +50 -0
- data/lib/delayed/backend/redis/destroy_job.lua +2 -0
- data/lib/delayed/backend/redis/enqueue.lua +29 -0
- data/lib/delayed/backend/redis/fail_job.lua +5 -0
- data/lib/delayed/backend/redis/find_available.lua +3 -0
- data/lib/delayed/backend/redis/functions.rb +57 -0
- data/lib/delayed/backend/redis/get_and_lock_next_available.lua +17 -0
- data/lib/delayed/backend/redis/includes/jobs_common.lua +203 -0
- data/lib/delayed/backend/redis/job.rb +497 -0
- data/lib/delayed/backend/redis/set_running.lua +5 -0
- data/lib/delayed/backend/redis/tickle_strand.lua +2 -0
- data/lib/delayed/batch.rb +56 -0
- data/lib/delayed/cli.rb +101 -0
- data/lib/delayed/daemon.rb +103 -0
- data/lib/delayed/engine.rb +4 -0
- data/lib/delayed/job_tracking.rb +31 -0
- data/lib/delayed/lifecycle.rb +90 -0
- data/lib/delayed/log_tailer.rb +22 -0
- data/lib/delayed/message_sending.rb +134 -0
- data/lib/delayed/performable_method.rb +52 -0
- data/lib/delayed/periodic.rb +85 -0
- data/lib/delayed/plugin.rb +22 -0
- data/lib/delayed/pool.rb +161 -0
- data/lib/delayed/server/helpers.rb +28 -0
- data/lib/delayed/server/public/css/app.css +12 -0
- data/lib/delayed/server/public/js/app.js +132 -0
- data/lib/delayed/server/views/index.erb +90 -0
- data/lib/delayed/server/views/layout.erb +47 -0
- data/lib/delayed/server.rb +120 -0
- data/lib/delayed/settings.rb +90 -0
- data/lib/delayed/testing.rb +32 -0
- data/lib/delayed/version.rb +3 -0
- data/lib/delayed/work_queue/in_process.rb +13 -0
- data/lib/delayed/work_queue/parent_process.rb +180 -0
- data/lib/delayed/worker.rb +234 -0
- data/lib/delayed/yaml_extensions.rb +109 -0
- data/lib/delayed_job.rb +46 -0
- data/lib/inst-jobs.rb +1 -0
- data/spec/active_record_job_spec.rb +246 -0
- data/spec/delayed/cli_spec.rb +23 -0
- data/spec/delayed/daemon_spec.rb +35 -0
- data/spec/delayed/server_spec.rb +63 -0
- data/spec/delayed/settings_spec.rb +32 -0
- data/spec/delayed/work_queue/in_process_spec.rb +31 -0
- data/spec/delayed/work_queue/parent_process_spec.rb +159 -0
- data/spec/delayed/worker_spec.rb +16 -0
- data/spec/gemfiles/32.gemfile +6 -0
- data/spec/gemfiles/40.gemfile +5 -0
- data/spec/gemfiles/41.gemfile +5 -0
- data/spec/gemfiles/42.gemfile +5 -0
- data/spec/migrate/20140924140513_add_story_table.rb +7 -0
- data/spec/redis_job_spec.rb +140 -0
- data/spec/sample_jobs.rb +28 -0
- data/spec/shared/delayed_batch.rb +85 -0
- data/spec/shared/delayed_method.rb +419 -0
- data/spec/shared/performable_method.rb +66 -0
- data/spec/shared/shared_backend.rb +819 -0
- data/spec/shared/testing.rb +48 -0
- data/spec/shared/worker.rb +378 -0
- data/spec/shared_jobs_specs.rb +15 -0
- data/spec/spec_helper.rb +97 -0
- metadata +390 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
require File.expand_path("../spec_helper", __FILE__)
|
|
2
|
+
|
|
3
|
+
describe 'Delayed::Backed::ActiveRecord::Job' do
|
|
4
|
+
before :all do
|
|
5
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
after :all do
|
|
9
|
+
Delayed.send(:remove_const, :Job)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
Delayed::Testing.clear_all!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
include_examples 'a delayed_jobs implementation'
|
|
17
|
+
|
|
18
|
+
it "should recover as well as possible from a failure failing a job" do
|
|
19
|
+
allow(Delayed::Job::Failed).to receive(:create).and_raise(RuntimeError)
|
|
20
|
+
job = "test".send_later_enqueue_args :reverse, no_delay: true
|
|
21
|
+
job_id = job.id
|
|
22
|
+
proc { job.fail! }.should raise_error(RuntimeError)
|
|
23
|
+
proc { Delayed::Job.find(job_id) }.should raise_error(ActiveRecord::RecordNotFound)
|
|
24
|
+
Delayed::Job.count.should == 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
context "when another worker has worked on a task since the job was found to be available, it" do
|
|
28
|
+
before :each do
|
|
29
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
|
30
|
+
@job_copy_for_worker_2 = Delayed::Job.find(@job.id)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "should not allow a second worker to get exclusive access if already successfully processed by worker1" do
|
|
34
|
+
@job.destroy
|
|
35
|
+
@job_copy_for_worker_2.send(:lock_exclusively!, 'worker2').should == false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
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
|
|
39
|
+
@job.update_attributes(:attempts => 1, :run_at => 1.day.from_now)
|
|
40
|
+
@job_copy_for_worker_2.send(:lock_exclusively!, 'worker2').should == false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "should select the next job at random if enabled" do
|
|
44
|
+
begin
|
|
45
|
+
Delayed::Settings.select_random_from_batch = true
|
|
46
|
+
15.times { "test".send_later :length }
|
|
47
|
+
founds = []
|
|
48
|
+
15.times do
|
|
49
|
+
job = Delayed::Job.get_and_lock_next_available('tester')
|
|
50
|
+
founds << job
|
|
51
|
+
job.unlock
|
|
52
|
+
job.save!
|
|
53
|
+
end
|
|
54
|
+
founds.uniq.size.should > 1
|
|
55
|
+
ensure
|
|
56
|
+
Delayed::Settings.select_random_from_batch = false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "should unlock a successfully locked job and persist the job's unlocked state" do
|
|
62
|
+
job = Delayed::Job.create :payload_object => SimpleJob.new
|
|
63
|
+
job.send(:lock_exclusively!, 'worker1').should == true
|
|
64
|
+
job.unlock
|
|
65
|
+
job.save!
|
|
66
|
+
job.reload
|
|
67
|
+
job.locked_by.should == nil
|
|
68
|
+
job.locked_at.should == nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "bulk_update failed jobs" do
|
|
72
|
+
context "holding/unholding failed jobs" do
|
|
73
|
+
before :each do
|
|
74
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
|
75
|
+
Delayed::Job.get_and_lock_next_available('worker1').should == @job
|
|
76
|
+
@job.fail!
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "should raise error when holding failed jobs" do
|
|
80
|
+
expect { Delayed::Job.bulk_update('hold', :flavor => 'failed', :query => @query) }.to raise_error(RuntimeError)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
it "should raise error unholding failed jobs" do
|
|
84
|
+
expect { Delayed::Job.bulk_update('unhold', :flavor => 'failed', :query => @query) }.to raise_error(RuntimeError)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
context "deleting failed jobs" do
|
|
89
|
+
before :each do
|
|
90
|
+
2.times {
|
|
91
|
+
j = Delayed::Job.create(:payload_object => SimpleJob.new)
|
|
92
|
+
j.send(:lock_exclusively!, 'worker1').should == true
|
|
93
|
+
j.fail!
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it "should delete failed jobs by id" do
|
|
98
|
+
target_ids = Delayed::Job::Failed.all[0..2].map { |j| j.id }
|
|
99
|
+
Delayed::Job.bulk_update('destroy', :ids => target_ids, :flavor => 'failed', :query => @query).should == target_ids.length
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "should delete all failed jobs" do
|
|
103
|
+
failed_count = Delayed::Job::Failed.count
|
|
104
|
+
Delayed::Job.bulk_update('destroy', :flavor => 'failed', :query => @query).should == failed_count
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
context 'n_strand' do
|
|
110
|
+
it "should default to 1" do
|
|
111
|
+
expect(Delayed::Job).to receive(:rand).never
|
|
112
|
+
job = Delayed::Job.enqueue(SimpleJob.new, :n_strand => 'njobs')
|
|
113
|
+
job.strand.should == "njobs"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "should set max_concurrent based on num_strands" do
|
|
117
|
+
change_setting(Delayed::Settings, :num_strands, ->(strand_name) { expect(strand_name).to eql "njobs"; "3" }) do
|
|
118
|
+
job = Delayed::Job.enqueue(SimpleJob.new, :n_strand => 'njobs')
|
|
119
|
+
job.strand.should == "njobs"
|
|
120
|
+
job.max_concurrent.should == 3
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "with two parameters" do
|
|
125
|
+
it "should use the first param as the setting to read" do
|
|
126
|
+
job = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs", "123"])
|
|
127
|
+
job.strand.should == "njobs/123"
|
|
128
|
+
change_setting(Delayed::Settings, :num_strands, ->(strand_name) {
|
|
129
|
+
case strand_name
|
|
130
|
+
when "njobs"; 3
|
|
131
|
+
else nil
|
|
132
|
+
end
|
|
133
|
+
}) do
|
|
134
|
+
job = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs", "123"])
|
|
135
|
+
job.strand.should == "njobs/123"
|
|
136
|
+
job.max_concurrent.should == 3
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "should allow overridding the setting based on the second param" do
|
|
141
|
+
change_setting(Delayed::Settings, :num_strands, ->(strand_name) {
|
|
142
|
+
case strand_name
|
|
143
|
+
when "njobs/123"; 5
|
|
144
|
+
else nil
|
|
145
|
+
end
|
|
146
|
+
}) do
|
|
147
|
+
job = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs", "123"])
|
|
148
|
+
job.strand.should == "njobs/123"
|
|
149
|
+
job.max_concurrent.should == 5
|
|
150
|
+
job = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs", "456"])
|
|
151
|
+
job.strand.should == "njobs/456"
|
|
152
|
+
job.max_concurrent.should == 1
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
change_setting(Delayed::Settings, :num_strands, ->(strand_name) {
|
|
156
|
+
case strand_name
|
|
157
|
+
when "njobs/123"; 5
|
|
158
|
+
when "njobs"; 3
|
|
159
|
+
else nil
|
|
160
|
+
end
|
|
161
|
+
}) do
|
|
162
|
+
job = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs", "123"])
|
|
163
|
+
job.strand.should == "njobs/123"
|
|
164
|
+
job.max_concurrent.should == 5
|
|
165
|
+
job = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs", "456"])
|
|
166
|
+
job.strand.should == "njobs/456"
|
|
167
|
+
job.max_concurrent.should == 3
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
context "max_concurrent triggers" do
|
|
173
|
+
before do
|
|
174
|
+
skip("postgres specific") unless ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
it "should set one job as next_in_strand at a time with max_concurrent of 1" do
|
|
178
|
+
job1 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
179
|
+
job1.reload
|
|
180
|
+
job1.next_in_strand.should == true
|
|
181
|
+
job2 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
182
|
+
job2.reload
|
|
183
|
+
job2.next_in_strand.should == false
|
|
184
|
+
run_job(job1)
|
|
185
|
+
job2.reload
|
|
186
|
+
job2.next_in_strand.should == true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
it "should set multiple jobs as next_in_strand at a time based on max_concurrent" do
|
|
190
|
+
change_setting(Delayed::Settings, :num_strands, ->(strand_name) {
|
|
191
|
+
case strand_name
|
|
192
|
+
when "njobs"; 2
|
|
193
|
+
else nil
|
|
194
|
+
end
|
|
195
|
+
}) do
|
|
196
|
+
job1 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
197
|
+
job1.reload
|
|
198
|
+
job1.next_in_strand.should == true
|
|
199
|
+
job2 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
200
|
+
job2.reload
|
|
201
|
+
job2.next_in_strand.should == true
|
|
202
|
+
job3 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
203
|
+
job3.reload
|
|
204
|
+
job3.next_in_strand.should == false
|
|
205
|
+
run_job(job1)
|
|
206
|
+
job3.reload
|
|
207
|
+
job3.next_in_strand.should == true
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it "should set multiple jobs as next_in_strand at once if needed" do
|
|
212
|
+
change_setting(Delayed::Settings, :num_strands, ->(strand_name) {
|
|
213
|
+
case strand_name
|
|
214
|
+
when "njobs"; 2
|
|
215
|
+
else nil
|
|
216
|
+
end
|
|
217
|
+
}) do
|
|
218
|
+
job1 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
219
|
+
job1.reload
|
|
220
|
+
job1.next_in_strand.should == true
|
|
221
|
+
job2 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
222
|
+
job2.reload
|
|
223
|
+
job2.next_in_strand.should == true
|
|
224
|
+
|
|
225
|
+
job3 = Delayed::Job.enqueue(SimpleJob.new, n_strand: ["njobs"])
|
|
226
|
+
job3.reload
|
|
227
|
+
job3.next_in_strand.should == false
|
|
228
|
+
|
|
229
|
+
# manually unset next_in_strand
|
|
230
|
+
Delayed::Job.where(:id => job2).update_all(:next_in_strand => false)
|
|
231
|
+
job2.reload
|
|
232
|
+
job2.next_in_strand.should == false
|
|
233
|
+
|
|
234
|
+
run_job(job1) # should update both jobs
|
|
235
|
+
|
|
236
|
+
job3.reload
|
|
237
|
+
job3.next_in_strand.should == true
|
|
238
|
+
|
|
239
|
+
job2.reload
|
|
240
|
+
job2.next_in_strand.should == true
|
|
241
|
+
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Delayed::CLI do
|
|
4
|
+
describe '#parse_cli_options!' do
|
|
5
|
+
it 'correctly parses the --config option' do
|
|
6
|
+
cli = described_class.new(%w{run --config /path/to/some/file.yml})
|
|
7
|
+
options = cli.parse_cli_options!
|
|
8
|
+
expect(options).to include config_file: '/path/to/some/file.yml'
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#run' do
|
|
13
|
+
before do
|
|
14
|
+
expect(Delayed::Settings).to receive(:worker_config).and_return({})
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'prints help when no command is given' do
|
|
18
|
+
cli = described_class.new([])
|
|
19
|
+
expect(cli).to receive(:puts).with(/Usage/)
|
|
20
|
+
cli.run
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Delayed::Daemon do
|
|
4
|
+
let(:pid_folder) { "/test/pid/folder" }
|
|
5
|
+
let(:pid) { 9999 }
|
|
6
|
+
let(:subject) { described_class.new(pid_folder) }
|
|
7
|
+
|
|
8
|
+
before do
|
|
9
|
+
allow(subject).to receive(:pid).and_return(pid)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '#stop' do
|
|
13
|
+
it 'prints status if not running' do
|
|
14
|
+
expect(subject).to receive(:status).with(print: false, pid: pid).and_return(false)
|
|
15
|
+
expect(subject).to receive(:status).with(no_args)
|
|
16
|
+
expect(Process).to receive(:kill).never
|
|
17
|
+
subject.stop
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'prints status if draining' do
|
|
21
|
+
expect(subject).to receive(:status).with(print: false, pid: pid).and_return(:draining)
|
|
22
|
+
expect(subject).to receive(:status).with(no_args)
|
|
23
|
+
expect(Process).to receive(:kill).never
|
|
24
|
+
subject.stop
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'sends INT by default' do
|
|
28
|
+
expect(subject).to receive(:status).with(print: false, pid: pid).and_return(:running)
|
|
29
|
+
expect(subject).to receive(:puts).with(/Stopping pool/)
|
|
30
|
+
expect(Process).to receive(:kill).with('INT', pid)
|
|
31
|
+
expect(subject).to receive(:wait).with(false)
|
|
32
|
+
subject.stop
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
require 'delayed/server'
|
|
3
|
+
|
|
4
|
+
RSpec.describe Delayed::Server, sinatra: true do
|
|
5
|
+
include Rack::Test::Methods
|
|
6
|
+
|
|
7
|
+
def app
|
|
8
|
+
described_class.new
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def parsed_body
|
|
12
|
+
@parsed_body ||= JSON.parse(last_response.body)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
before :all do
|
|
16
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
after :all do
|
|
20
|
+
Delayed.send(:remove_const, :Job)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "get '/running'" do
|
|
24
|
+
before do
|
|
25
|
+
3.times do |i|
|
|
26
|
+
Delayed::Job.create!({
|
|
27
|
+
run_at: Time.now,
|
|
28
|
+
locked_at: Time.now,
|
|
29
|
+
locked_by: "dummy-runner-#{i}:${$$}",
|
|
30
|
+
})
|
|
31
|
+
end
|
|
32
|
+
get '/running'
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'must return a json object with the running job data in an array', aggregate_failures: true do
|
|
36
|
+
expect(last_response).to be_ok
|
|
37
|
+
expect(parsed_body['data']).to be_an Array
|
|
38
|
+
expect(parsed_body['data'].size).to eq 3
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe "get '/jobs'" do
|
|
43
|
+
let!(:job_1) { Delayed::Job.enqueue(SimpleJob.new, strand: 'strand-1') }
|
|
44
|
+
let!(:job_2) { Delayed::Job.enqueue(SimpleJob.new, strand: 'strand-2') }
|
|
45
|
+
let!(:job_3) { Delayed::Job.enqueue(SimpleJob.new, strand: 'strand-3') }
|
|
46
|
+
|
|
47
|
+
context 'with the flavor param set to id' do
|
|
48
|
+
before do
|
|
49
|
+
get "/jobs?flavor=id&search_term=#{job_2.id}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'must only return the job with the id specified in the search_term param' do
|
|
53
|
+
jobs = parsed_body['data']
|
|
54
|
+
job_ids = jobs.map{ |j| j['id'] }
|
|
55
|
+
expect(job_ids).to eq [job_2.id]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'must set recordsFiltered in the response to 1' do
|
|
59
|
+
expect(parsed_body['recordsFiltered']).to eq 1
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Delayed::Settings do
|
|
4
|
+
let(:configfile) {<<-YAML
|
|
5
|
+
default:
|
|
6
|
+
workers:
|
|
7
|
+
- queue: myqueue
|
|
8
|
+
workers: 2
|
|
9
|
+
- queue: secondqueue
|
|
10
|
+
max_priority: 7
|
|
11
|
+
max_attempts: 1
|
|
12
|
+
YAML
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe '.worker_config' do
|
|
16
|
+
it 'merges each worker config with the top-level config' do
|
|
17
|
+
expect(File).to receive(:read).with("fname").and_return(configfile)
|
|
18
|
+
config = described_class.worker_config("fname")
|
|
19
|
+
expect(config[:workers]).to eq([
|
|
20
|
+
{'queue' => 'myqueue', 'workers' => 2, 'max_attempts' => 1},
|
|
21
|
+
{'queue' => 'secondqueue', 'max_priority' => 7, 'max_attempts' => 1},
|
|
22
|
+
])
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '.apply_worker_config!' do
|
|
27
|
+
it 'applies global settings from the given config' do
|
|
28
|
+
expect(described_class).to receive(:last_ditch_logfile=).with(true)
|
|
29
|
+
described_class.apply_worker_config!('last_ditch_logfile' => true)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Delayed::WorkQueue::InProcess do
|
|
4
|
+
before :all do
|
|
5
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
after :all do
|
|
9
|
+
Delayed.send(:remove_const, :Job)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after :each do
|
|
13
|
+
Delayed::Worker.lifecycle.reset!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
let(:subject) { described_class.new }
|
|
17
|
+
let(:args) { ["worker_name", "queue_name", 1, 2] }
|
|
18
|
+
|
|
19
|
+
it 'triggers the lifecycle event around the pop' do
|
|
20
|
+
called = false
|
|
21
|
+
Delayed::Worker.lifecycle.around(:work_queue_pop) do |queue, &cb|
|
|
22
|
+
expect(queue).to eq(subject)
|
|
23
|
+
expect(Delayed::Job).to receive(:get_and_lock_next_available).with(*args).and_return(:job)
|
|
24
|
+
called = true
|
|
25
|
+
cb.call(queue)
|
|
26
|
+
end
|
|
27
|
+
job = subject.get_and_lock_next_available(*args)
|
|
28
|
+
expect(job).to eq(:job)
|
|
29
|
+
expect(called).to eq(true)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
RSpec.describe Delayed::WorkQueue::ParentProcess do
|
|
4
|
+
before :all do
|
|
5
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
after :all do
|
|
9
|
+
Delayed.send(:remove_const, :Job)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
after :each do
|
|
13
|
+
Delayed::Worker.lifecycle.reset!
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
let(:subject) { described_class.new }
|
|
17
|
+
|
|
18
|
+
it 'generates a server listening on a valid unix socket' do
|
|
19
|
+
server = subject.server
|
|
20
|
+
expect(server).to be_a(Delayed::WorkQueue::ParentProcess::Server)
|
|
21
|
+
expect(server.listen_socket.local_address.unix?).to be(true)
|
|
22
|
+
expect { server.listen_socket.accept_nonblock }.to raise_error(IO::WaitReadable)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'generates a client connected to the server unix socket' do
|
|
26
|
+
server = subject.server
|
|
27
|
+
client = subject.client
|
|
28
|
+
expect(client).to be_a(Delayed::WorkQueue::ParentProcess::Client)
|
|
29
|
+
expect(client.addrinfo.unix?).to be(true)
|
|
30
|
+
expect(client.addrinfo.unix_path).to eq(server.listen_socket.local_address.unix_path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
describe Delayed::WorkQueue::ParentProcess::Client do
|
|
34
|
+
let(:subject) { described_class.new(addrinfo) }
|
|
35
|
+
let(:addrinfo) { double('Addrinfo') }
|
|
36
|
+
let(:connection) { double('Socket') }
|
|
37
|
+
let(:args) { ["worker_name", "queue_name", 1, 2] }
|
|
38
|
+
let(:job) { Delayed::Job.new(locked_by: "worker_name") }
|
|
39
|
+
|
|
40
|
+
it 'marshals the given arguments to the server and returns the response' do
|
|
41
|
+
expect(addrinfo).to receive(:connect).once.and_return(connection)
|
|
42
|
+
expect(Marshal).to receive(:dump).with(args, connection).ordered
|
|
43
|
+
expect(Marshal).to receive(:load).with(connection).and_return(job).ordered
|
|
44
|
+
response = subject.get_and_lock_next_available(*args)
|
|
45
|
+
expect(response).to eq(job)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it 'returns nil and then reconnects on socket error' do
|
|
49
|
+
expect(addrinfo).to receive(:connect).once.and_return(connection)
|
|
50
|
+
expect(Marshal).to receive(:dump).and_raise(SystemCallError.new("failure"))
|
|
51
|
+
response = subject.get_and_lock_next_available(*args)
|
|
52
|
+
expect(response).to be_nil
|
|
53
|
+
|
|
54
|
+
expect(addrinfo).to receive(:connect).once.and_return(connection)
|
|
55
|
+
expect(Marshal).to receive(:dump).with(args, connection)
|
|
56
|
+
expect(Marshal).to receive(:load).with(connection).and_return(job)
|
|
57
|
+
response = subject.get_and_lock_next_available(*args)
|
|
58
|
+
expect(response).to eq(job)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'errors if the response is not a locked job' do
|
|
62
|
+
expect(addrinfo).to receive(:connect).once.and_return(connection)
|
|
63
|
+
expect(Marshal).to receive(:dump).with(args, connection)
|
|
64
|
+
expect(Marshal).to receive(:load).with(connection).and_return(:not_a_job)
|
|
65
|
+
expect { subject.get_and_lock_next_available(*args) }.to raise_error(Delayed::WorkQueue::ParentProcess::ProtocolError)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
it 'errors if the response is a job not locked by this worker' do
|
|
69
|
+
expect(addrinfo).to receive(:connect).once.and_return(connection)
|
|
70
|
+
expect(Marshal).to receive(:dump).with(args, connection)
|
|
71
|
+
job.locked_by = "somebody_else"
|
|
72
|
+
expect(Marshal).to receive(:load).with(connection).and_return(job)
|
|
73
|
+
expect { subject.get_and_lock_next_available(*args) }.to raise_error(Delayed::WorkQueue::ParentProcess::ProtocolError)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe Delayed::WorkQueue::ParentProcess::Server do
|
|
78
|
+
let(:subject) { described_class.new(listen_socket) }
|
|
79
|
+
let(:listen_socket) { Socket.unix_server_socket(Delayed::WorkQueue::ParentProcess.generate_socket_path) }
|
|
80
|
+
let(:args) { [1,2,3] }
|
|
81
|
+
let(:job) { :a_job }
|
|
82
|
+
|
|
83
|
+
it 'accepts new clients' do
|
|
84
|
+
client = Socket.unix(subject.listen_socket.local_address.unix_path)
|
|
85
|
+
expect { subject.run_once }.to change(subject, :connected_clients).by(1)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'queries the queue on client request' do
|
|
89
|
+
client = Socket.unix(subject.listen_socket.local_address.unix_path)
|
|
90
|
+
subject.run_once
|
|
91
|
+
|
|
92
|
+
expect(Delayed::Job).to receive(:get_and_lock_next_available).with(*args).and_return(job)
|
|
93
|
+
Marshal.dump(args, client)
|
|
94
|
+
subject.run_once
|
|
95
|
+
expect(Marshal.load(client)).to eq(job)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'drops the client on i/o error' do
|
|
99
|
+
client = Socket.unix(subject.listen_socket.local_address.unix_path)
|
|
100
|
+
subject.run_once
|
|
101
|
+
|
|
102
|
+
Marshal.dump(args, client)
|
|
103
|
+
|
|
104
|
+
expect(Marshal).to receive(:load).and_raise(IOError.new("socket went away"))
|
|
105
|
+
expect { subject.run_once }.to change(subject, :connected_clients).by(-1)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it 'drops the client on timeout' do
|
|
109
|
+
client = Socket.unix(subject.listen_socket.local_address.unix_path)
|
|
110
|
+
subject.run_once
|
|
111
|
+
|
|
112
|
+
Marshal.dump(args, client)
|
|
113
|
+
|
|
114
|
+
expect(Marshal).to receive(:load).and_raise(Timeout::Error.new("socket timed out"))
|
|
115
|
+
expect(Timeout).to receive(:timeout).with(Delayed::Settings.parent_process_client_timeout).and_yield
|
|
116
|
+
expect { subject.run_once }.to change(subject, :connected_clients).by(-1)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
it 'tracks when clients are idle' do
|
|
120
|
+
expect(subject.all_workers_idle?).to be(true)
|
|
121
|
+
|
|
122
|
+
client = Socket.unix(subject.listen_socket.local_address.unix_path)
|
|
123
|
+
subject.run_once
|
|
124
|
+
expect(subject.all_workers_idle?).to be(true)
|
|
125
|
+
|
|
126
|
+
expect(Delayed::Job).to receive(:get_and_lock_next_available).with(*args).and_return(job)
|
|
127
|
+
Marshal.dump(args, client)
|
|
128
|
+
subject.run_once
|
|
129
|
+
expect(subject.all_workers_idle?).to be(false)
|
|
130
|
+
|
|
131
|
+
expect(Delayed::Job).to receive(:get_and_lock_next_available).with(*args).and_return(nil)
|
|
132
|
+
Marshal.dump(args, client)
|
|
133
|
+
subject.run_once
|
|
134
|
+
expect(subject.all_workers_idle?).to be(true)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'triggers the lifecycle event around the pop' do
|
|
138
|
+
called = false
|
|
139
|
+
client = Socket.unix(subject.listen_socket.local_address.unix_path)
|
|
140
|
+
subject.run_once
|
|
141
|
+
|
|
142
|
+
Delayed::Worker.lifecycle.around(:work_queue_pop) do |queue, &cb|
|
|
143
|
+
expect(subject.all_workers_idle?).to be(true)
|
|
144
|
+
expect(queue).to eq(subject)
|
|
145
|
+
expect(Delayed::Job).to receive(:get_and_lock_next_available).with(*args).and_return(job)
|
|
146
|
+
called = true
|
|
147
|
+
res = cb.call(queue)
|
|
148
|
+
expect(subject.all_workers_idle?).to be(false)
|
|
149
|
+
res
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
Marshal.dump(args, client)
|
|
153
|
+
subject.run_once
|
|
154
|
+
|
|
155
|
+
expect(Marshal.load(client)).to eq(job)
|
|
156
|
+
expect(called).to eq(true)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
module Delayed
|
|
4
|
+
describe Worker do
|
|
5
|
+
describe "#perform" do
|
|
6
|
+
it "fires off an error callback when a job raises an exception" do
|
|
7
|
+
fired = false
|
|
8
|
+
Worker.lifecycle.before(:error) {|worker, exception| fired = true}
|
|
9
|
+
worker = Worker.new
|
|
10
|
+
job = double(:last_error= => nil, attempts: 1, reschedule: nil)
|
|
11
|
+
worker.perform(job)
|
|
12
|
+
expect(fired).to be_truthy
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|