inst-jobs 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/bin/inst_job +4 -0
  3. data/db/migrate/20101216224513_create_delayed_jobs.rb +40 -0
  4. data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +14 -0
  5. data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +13 -0
  6. data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +14 -0
  7. data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +26 -0
  8. data/db/migrate/20110610213249_optimize_delayed_jobs.rb +40 -0
  9. data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +52 -0
  10. data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +31 -0
  11. data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +15 -0
  12. data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +80 -0
  13. data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +15 -0
  14. data/db/migrate/20120608191051_add_jobs_run_at_index.rb +15 -0
  15. data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +13 -0
  16. data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +13 -0
  17. data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +13 -0
  18. data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +13 -0
  19. data/db/migrate/20140512213941_add_source_to_jobs.rb +15 -0
  20. data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +70 -0
  21. data/db/migrate/20151123210429_add_expires_at_to_jobs.rb +15 -0
  22. data/db/migrate/20151210162949_improve_max_concurrent.rb +50 -0
  23. data/lib/delayed/backend/active_record.rb +340 -0
  24. data/lib/delayed/backend/base.rb +335 -0
  25. data/lib/delayed/backend/redis/bulk_update.lua +50 -0
  26. data/lib/delayed/backend/redis/destroy_job.lua +2 -0
  27. data/lib/delayed/backend/redis/enqueue.lua +29 -0
  28. data/lib/delayed/backend/redis/fail_job.lua +5 -0
  29. data/lib/delayed/backend/redis/find_available.lua +3 -0
  30. data/lib/delayed/backend/redis/functions.rb +57 -0
  31. data/lib/delayed/backend/redis/get_and_lock_next_available.lua +17 -0
  32. data/lib/delayed/backend/redis/includes/jobs_common.lua +203 -0
  33. data/lib/delayed/backend/redis/job.rb +497 -0
  34. data/lib/delayed/backend/redis/set_running.lua +5 -0
  35. data/lib/delayed/backend/redis/tickle_strand.lua +2 -0
  36. data/lib/delayed/batch.rb +56 -0
  37. data/lib/delayed/cli.rb +101 -0
  38. data/lib/delayed/daemon.rb +103 -0
  39. data/lib/delayed/engine.rb +4 -0
  40. data/lib/delayed/job_tracking.rb +31 -0
  41. data/lib/delayed/lifecycle.rb +90 -0
  42. data/lib/delayed/log_tailer.rb +22 -0
  43. data/lib/delayed/message_sending.rb +134 -0
  44. data/lib/delayed/performable_method.rb +52 -0
  45. data/lib/delayed/periodic.rb +85 -0
  46. data/lib/delayed/plugin.rb +22 -0
  47. data/lib/delayed/pool.rb +161 -0
  48. data/lib/delayed/server/helpers.rb +28 -0
  49. data/lib/delayed/server/public/css/app.css +12 -0
  50. data/lib/delayed/server/public/js/app.js +132 -0
  51. data/lib/delayed/server/views/index.erb +90 -0
  52. data/lib/delayed/server/views/layout.erb +47 -0
  53. data/lib/delayed/server.rb +120 -0
  54. data/lib/delayed/settings.rb +90 -0
  55. data/lib/delayed/testing.rb +32 -0
  56. data/lib/delayed/version.rb +3 -0
  57. data/lib/delayed/work_queue/in_process.rb +13 -0
  58. data/lib/delayed/work_queue/parent_process.rb +180 -0
  59. data/lib/delayed/worker.rb +234 -0
  60. data/lib/delayed/yaml_extensions.rb +109 -0
  61. data/lib/delayed_job.rb +46 -0
  62. data/lib/inst-jobs.rb +1 -0
  63. data/spec/active_record_job_spec.rb +246 -0
  64. data/spec/delayed/cli_spec.rb +23 -0
  65. data/spec/delayed/daemon_spec.rb +35 -0
  66. data/spec/delayed/server_spec.rb +63 -0
  67. data/spec/delayed/settings_spec.rb +32 -0
  68. data/spec/delayed/work_queue/in_process_spec.rb +31 -0
  69. data/spec/delayed/work_queue/parent_process_spec.rb +159 -0
  70. data/spec/delayed/worker_spec.rb +16 -0
  71. data/spec/gemfiles/32.gemfile +6 -0
  72. data/spec/gemfiles/40.gemfile +5 -0
  73. data/spec/gemfiles/41.gemfile +5 -0
  74. data/spec/gemfiles/42.gemfile +5 -0
  75. data/spec/migrate/20140924140513_add_story_table.rb +7 -0
  76. data/spec/redis_job_spec.rb +140 -0
  77. data/spec/sample_jobs.rb +28 -0
  78. data/spec/shared/delayed_batch.rb +85 -0
  79. data/spec/shared/delayed_method.rb +419 -0
  80. data/spec/shared/performable_method.rb +66 -0
  81. data/spec/shared/shared_backend.rb +819 -0
  82. data/spec/shared/testing.rb +48 -0
  83. data/spec/shared/worker.rb +378 -0
  84. data/spec/shared_jobs_specs.rb +15 -0
  85. data/spec/spec_helper.rb +97 -0
  86. 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
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 3.2.19"
6
+
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 4.0.10"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 4.1.6"
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec :path=>"../../"
4
+
5
+ gem "rails", "~> 4.2.5"
@@ -0,0 +1,7 @@
1
+ class AddStoryTable < ActiveRecord::Migration
2
+ def change
3
+ create_table :stories do |table|
4
+ table.string :text
5
+ end
6
+ end
7
+ end