Skip to content

Commit 1b97013

Browse files
committed
Drain local workers by working off remaining jobs on SIGTERM
Local workers handle jobs that require local filesystem access (package, droplet, and buildpack uploads). When a local worker receives SIGTERM during drain, the default delayed_job handler calls stop(), which exits after the current job but leaves remaining queued jobs unprocessed until the worker restarts. This adds LocalWorkerDrainPlugin, which replaces the SIGTERM handler for local workers only. Instead of stopping, the worker sets exit_on_complete so it finishes all remaining jobs in the queue before exiting, avoiding dangling PROCESSING_UPLOAD/PENDING states across restarts. The plugin is a no-op for generic workers and named clock queues, which retain the default SIGTERM behaviour.
1 parent 51362aa commit 1b97013

3 files changed

Lines changed: 55 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Replaces the default SIGTERM handler for local workers to drain all remaining jobs before exiting.
2+
class LocalWorkerDrainPlugin < Delayed::Plugin
3+
callbacks do |lifecycle|
4+
lifecycle.before(:execute) do |worker|
5+
trap('TERM') do
6+
Thread.new { worker.say 'Draining: will exit after finishing remaining jobs' }
7+
worker.class.exit_on_complete = true
8+
end
9+
end
10+
end
11+
end
12+
13+
Delayed::Worker.plugins << LocalWorkerDrainPlugin

lib/tasks/jobs.rake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ namespace :jobs do
2626
desc 'Start a delayed_job worker that works on jobs that require access to local resources.'
2727

2828
task :local, [:name] => :environment do |_t, args|
29+
require 'delayed_job/local_worker_drain_plugin'
2930
puts RUBY_DESCRIPTION
3031
queue = VCAP::CloudController::Jobs::Queues.local(RakeConfig.config).to_s
3132
args.with_defaults(name: queue)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
require 'spec_helper'
2+
require 'delayed_job/local_worker_drain_plugin'
3+
4+
RSpec.describe LocalWorkerDrainPlugin do
5+
let(:worker) { Delayed::Worker.new }
6+
7+
before do
8+
Delayed::Worker.exit_on_complete = false
9+
allow(worker).to receive(:reload!)
10+
allow(worker).to receive(:say)
11+
end
12+
13+
after do
14+
Delayed::Worker.exit_on_complete = false
15+
Delayed::Worker.plugins.delete(LocalWorkerDrainPlugin)
16+
end
17+
18+
describe 'TERM signal handling' do
19+
before do
20+
Delayed::Worker.sleep_delay = 0
21+
end
22+
23+
after { Delayed::Worker.sleep_delay = Delayed::Worker::DEFAULT_SLEEP_DELAY }
24+
25+
it 'works off all remaining jobs in the queue before exiting' do
26+
work_off_calls = Queue.new
27+
allow(worker).to receive(:work_off) do
28+
work_off_calls.push(:called)
29+
work_off_calls.size < 3 ? [1, 0] : [0, 0]
30+
end
31+
32+
worker_thread = Thread.new { worker.start }
33+
work_off_calls.pop # wait until worker has started
34+
Process.kill('TERM', Process.pid)
35+
worker_thread.join(5)
36+
37+
expect(worker_thread.alive?).to be(false)
38+
expect(work_off_calls.size).to eq(3)
39+
end
40+
end
41+
end

0 commit comments

Comments
 (0)