Release blocked executions whose job class no longer resolves - #787
Open
wintan1418 wants to merge 1 commit into
Open
Release blocked executions whose job class no longer resolves#787wintan1418 wants to merge 1 commit into
wintan1418 wants to merge 1 commit into
Conversation
BlockedExecution#release called Semaphore.wait(job) unconditionally, and the semaphore proxy dereferences job.concurrency_limit and job.concurrency_duration, which are delegated to job_class — nil when the job's class was renamed or removed between deploys. The release transaction raised DelegationError and rolled back, so the dispatcher's concurrency maintenance picked up the same blocked execution and crashed again on every tick, forever, and the row was never cleaned up. Job#concurrency_limited? already treats jobs without a resolvable class as not limited, and Job#acquire_concurrency_lock consults it before touching the semaphore; the blocked execution release path was the only one that didn't. Apply the same guard there: promote the execution to ready without taking a semaphore slot, so the job fails on execution with a proper NameError and becomes visible as a failed execution instead of wedging the dispatcher. Fixes rails#784
wintan1418
force-pushed
the
guard-blocked-release-against-missing-job-class
branch
from
August 18, 2026 18:55
3deff3d to
fb24d2c
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #784
Problem
When a
SolidQueue::BlockedExecutionrow exists for a job whoseclass_nameno longersafe_constantizes (class renamed, removed, or re-namespaced between deploys), the dispatcher's concurrency maintenance crashes on every tick, forever:BlockedExecution#releasecallsSemaphore.wait(job)unconditionally, andSemaphore::Proxy#limit/#expires_atdereferencejob.concurrency_limit/job.concurrency_duration, which are delegated tojob_class—nilin this scenario. The release transaction rolls back, sounblock_blocked_executionspicks up the same row again on the next tick and the orphan is never released or cleaned up.Fix
Job#concurrency_limited?already treats jobs without a resolvable class as not concurrency-limited, andJob#acquire_concurrency_lockconsults it before touching the semaphore — the blocked execution release path was the only one that didn't. This applies the same guard there: the execution is promoted to ready without taking a semaphore slot, so the job fails on execution with a properNameErrorand shows up as a failed execution (visible, retriable, discardable), instead of wedging the dispatcher indefinitely.Test
The regression test reproduces the exact
DelegationErrorfrom the issue onmainand passes with the fix. It also asserts the semaphore value is untouched by the release, since there are no concurrency limits left to enforce for a class that is gone.