Skip to content

Commit cdf68fe

Browse files
committed
feat: warn at startup when max_claims >= DB connection pool size
1 parent f472191 commit cdf68fe

2 files changed

Lines changed: 82 additions & 0 deletions

File tree

lib/delayed/worker.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ def name
6363
# Setting the name to nil will reset the default worker name
6464
attr_writer :name
6565

66+
def start
67+
check_connection_pool_config!
68+
super
69+
end
70+
6671
def run!
6772
@realtime = Benchmark.realtime do
6873
@result = work_off
@@ -235,6 +240,20 @@ def reload!
235240
Rails.application.reloader.reload! if defined?(Rails.application.reloader) && Rails.application.reloader.check!
236241
end
237242

243+
def check_connection_pool_config!
244+
return unless Delayed::Job.respond_to?(:connection_pool)
245+
246+
pool_size = Delayed::Job.connection_pool.size
247+
return unless pool_size
248+
return if self.class.max_claims < pool_size
249+
250+
say "WARNING: max_claims (#{self.class.max_claims}) >= DB connection pool size (#{pool_size}). " \
251+
"The worker process needs at least 1 connection for its own housekeeping, so job threads may " \
252+
"starve waiting for a connection. Set Delayed::Worker.max_claims to at most #{[pool_size - 1, 1].max}.", 'warn'
253+
rescue StandardError
254+
nil
255+
end
256+
238257
def clock_time
239258
Process.clock_gettime(Process::CLOCK_MONOTONIC)
240259
end

spec/worker_spec.rb

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,69 @@
1919
expect(performances).to eq [true, nil, nil]
2020
expect(Delayed::Job).to have_received(:reserve)
2121
end
22+
23+
describe 'connection pool config check' do
24+
let(:connection_pool) { instance_double(ActiveRecord::ConnectionAdapters::ConnectionPool, size: pool_size) }
25+
26+
before do
27+
subject.send(:stop) # prevent start from running more than one loop
28+
allow(Delayed::Job).to receive(:reserve).and_return([])
29+
allow(Delayed::Job).to receive(:connection_pool).and_return(connection_pool)
30+
allow(Delayed::Job).to receive(:clear_locks!)
31+
allow(subject).to receive(:say).and_call_original
32+
end
33+
34+
around do |example|
35+
max_claims_was = described_class.max_claims
36+
described_class.max_claims = max_claims
37+
example.run
38+
ensure
39+
described_class.max_claims = max_claims_was
40+
end
41+
42+
context 'when max_claims equals pool size' do
43+
let(:max_claims) { 5 }
44+
let(:pool_size) { 5 }
45+
46+
it 'logs a warning at startup' do
47+
subject.start
48+
expect(subject).to have_received(:say).with(/WARNING.*max_claims.*5.*>=.*pool size.*5/i, 'warn')
49+
end
50+
end
51+
52+
context 'when max_claims exceeds pool size' do
53+
let(:max_claims) { 6 }
54+
let(:pool_size) { 5 }
55+
56+
it 'logs a warning at startup' do
57+
subject.start
58+
expect(subject).to have_received(:say).with(/WARNING.*max_claims.*6.*>=.*pool size.*5/i, 'warn')
59+
end
60+
end
61+
62+
context 'when max_claims is less than pool size' do
63+
let(:max_claims) { 4 }
64+
let(:pool_size) { 5 }
65+
66+
it 'does not log a warning' do
67+
subject.start
68+
expect(subject).not_to have_received(:say).with(/WARNING/i, 'warn')
69+
end
70+
end
71+
72+
context 'when connection_pool introspection raises' do
73+
let(:max_claims) { 5 }
74+
let(:pool_size) { 5 }
75+
76+
before do
77+
allow(Delayed::Job).to receive(:connection_pool).and_raise(StandardError, 'unavailable')
78+
end
79+
80+
it 'starts without raising' do
81+
expect { subject.start }.not_to raise_error
82+
end
83+
end
84+
end
2285
end
2386

2487
# rubocop:disable RSpec/SubjectStub

0 commit comments

Comments
 (0)