From 7c88af2c43e9ea4580545b367ef3fc1837da3c52 Mon Sep 17 00:00:00 2001 From: denderlin Date: Tue, 17 Jun 2025 18:58:09 +0200 Subject: [PATCH 1/2] Start jobs outside of transaction that is locking the job to prevent race-condition on job state --- lib/workhorse/poller.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/workhorse/poller.rb b/lib/workhorse/poller.rb index 3a9a383..e33a91b 100644 --- a/lib/workhorse/poller.rb +++ b/lib/workhorse/poller.rb @@ -206,9 +206,9 @@ def poll timeout = [MIN_LOCK_TIMEOUT, [MAX_LOCK_TIMEOUT, worker.polling_interval].min].max with_global_lock timeout: timeout do - Workhorse.tx_callback.call do - job_ids = [] + job_ids = [] + Workhorse.tx_callback.call do # As we are the only thread posting into the worker pool, it is safe to # get the number of idle threads without mutex synchronization. The # actual number of idle workers at time of posting can only be larger @@ -230,9 +230,14 @@ def poll worker.log 'Rolling back transaction to unlock jobs, as worker has been shut down in the meantime' fail ActiveRecord::Rollback end - - job_ids.each { |job_id| worker.perform(job_id) } end + + # This needs to be outside the above transaction because it runs the job + # in a new thread which opens a new connection. Even though it would be + # non-blocking and thus directly conclude the block and the transaction, + # there would still be a risk that the transaction is not committed yet + # when the job starts. + job_ids.each { |job_id| worker.perform(job_id) } if running? end end From 78055ba9edf64dafd646281eef11544b29706aa6 Mon Sep 17 00:00:00 2001 From: denderlin Date: Wed, 18 Jun 2025 12:11:07 +0200 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb99afa..6633dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Workhorse Changelog +## Unreleased + +* Fix race-condition in polling mechanism which could result in workers + trying to run a job that is not yet locked. + + Sitrox reference: #128333. + ## 1.3.0.rc3 - 2025-06-10 * Require Rails 7.0.0 or later