Keep concurrency locks across non-graceful job release - #771
Keep concurrency locks across non-graceful job release#771rafael-pissardo wants to merge 2 commits into
Conversation
When a claimed concurrency-limited job is released back to ready after a force-kill, its semaphore could expire and maintenance could unblock another job for the same key while the released job was still ready. Workers then claimed both and broke limits_concurrency. Extend the lock on release, skip expiring semaphores still held by ready jobs, run concurrency maintenance synchronously at supervisor boot and dispatcher start, and cover the race with unit tests.
|
Hey @rafael-pissardo, thanks for this, but I'm not sure these changes are compatible with the way concurrency controls work. What they fix is intentional behaviour.
The expiration is based on the max duration specified in the job's concurrency settings. This means that after that time has passed, we can no longer guarantee that other jobs will run. This is the case no matter what the job is doing. If the job went back to ready, that means that either the worker crashed, or it was gracefully terminated with the job going over the shutdown timeout, or something similar. If the lock expires during that time, we consider that to be the time the job takes to run, and it counts towards the concurrency duration. The concurrency duration is a self-healing mechanism to avoid having jobs blocked forever. The trade-off is that in some cases, depending on the jobs and the duration configured, it might happen that some jobs overlap. This is preferable to having jobs blocked forever.
This is the same idea. If we have ready jobs when their locks have already expired, then they need to overlap with other jobs, that's the way it's intended to work. From the README:
Thank you! |
Summary
Fixes #735.
Test plan
TARGET_DB=sqlite bundle exec ruby -Itest test/unit/concurrency_maintenance_test.rbTARGET_DB=sqlite bundle exec ruby -Itest test/integration/concurrency_controls_test.rbTARGET_DB=sqlite bundle exec ruby -Itest test/models/solid_queue/claimed_execution_test.rbTARGET_DB=sqlite bundle exec ruby -Itest test/unit/dispatcher_test.rbTARGET_DB=sqlite bundle exec ruby -Itest test/unit/fork_supervisor_test.rb