From 3ccd51253dbe32b1bb5d85aa5cdfa59a1b9a70c2 Mon Sep 17 00:00:00 2001 From: Philipp Thun Date: Thu, 27 Aug 2026 14:09:46 +0200 Subject: [PATCH] Make drain plugin drain spec deterministic The 'works off all remaining jobs before exiting' example raced the worker loop against TERM signal delivery. On slow/contended runners the drain flag (exit_on_complete) flipped late, so work_off ran extra times and the count inflated past 3. Block the worker after its first work_off call until the test confirms the drain TERM handler has set exit_on_complete, then release it. This removes the timing dependency and makes the call count deterministic. --- .../local_worker_drain_plugin_spec.rb | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/spec/unit/lib/delayed_job/local_worker_drain_plugin_spec.rb b/spec/unit/lib/delayed_job/local_worker_drain_plugin_spec.rb index 5fb09db1cac..6a873c8a0cf 100644 --- a/spec/unit/lib/delayed_job/local_worker_drain_plugin_spec.rb +++ b/spec/unit/lib/delayed_job/local_worker_drain_plugin_spec.rb @@ -23,19 +23,37 @@ after { Delayed::Worker.sleep_delay = Delayed::Worker::DEFAULT_SLEEP_DELAY } it 'works off all remaining jobs in the queue before exiting' do - work_off_calls = Queue.new + work_off_count = 0 + worker_started = Queue.new + resume_worker = Queue.new allow(worker).to receive(:work_off) do - work_off_calls.push(:called) - work_off_calls.size < 3 ? [1, 0] : [0, 0] + work_off_count += 1 + if work_off_count == 1 + worker_started.push(:yes) + # Block after the first call until the test has confirmed the TERM handler ran, + # so the number of work_off calls does not depend on signal-delivery timing. + resume_worker.pop + end + work_off_count < 3 ? [1, 0] : [0, 0] end worker_thread = Thread.new { worker.start } - work_off_calls.pop # wait until worker has started + worker_started.pop # wait until worker has started + Process.kill('TERM', Process.pid) + # Wait (max. 1s) until the drain TERM handler has actually flipped the flag. + deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 1 + until Delayed::Worker.exit_on_complete + raise 'Timed out waiting for Delayed::Worker.exit_on_complete' if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline + + sleep 0.01 + end + resume_worker.push(:now) + worker_thread.join(5) expect(worker_thread.alive?).to be(false) - expect(work_off_calls.size).to eq(3) + expect(work_off_count).to eq(3) end end end