Skip to content

Commit fcdab10

Browse files
rosaclaude
andcommitted
Test that a job records the semaphore it created on MySQL, and correct the upgrade note
A plain read inside a REPEATABLE READ transaction can still see a semaphore deleted after the transaction's snapshot, so without the locking read the job would record that one. The test fails without it. A slot that isn't given back comes back only when its semaphore expires, and every job taking or giving back a slot for the same key pushes that out, so on a busy key with a limit above 1 it may not come back for a long time. Say so in UPGRADING instead of promising it comes back after duration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5fd0653 commit fcdab10

2 files changed

Lines changed: 24 additions & 1 deletion

File tree

‎UPGRADING.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Jobs with [concurrency controls](README.md#concurrency-controls) now remember wh
33

44
There's nothing to change or migrate. Jobs that got their slot before you upgrade have nothing recorded and give it back as before, so old and new versions can run side by side while you deploy.
55

6-
Solid Queue 2.0 will stop giving back slots that weren't recorded. Before upgrading to 2.0, let the concurrency-limited jobs that were ready or running when you upgraded to 1.7.2 finish. If any haven't, their slots stay taken until their semaphores expire, after `duration`, and then the jobs waiting for them are released as usual.
6+
Solid Queue 2.0 will stop giving back slots that weren't recorded. Before upgrading to 2.0, make sure the concurrency-limited jobs that were ready or running when you upgraded to 1.7.2 have finished. Otherwise their slots won't be given back: a slot like that only comes back when its semaphore expires, and every job that takes or gives back a slot for the same key pushes that expiry out by another `duration`, so on a busy key with a limit above 1 it can stay lost for as long as the key stays busy.
77

88
# Upgrading to version 1.7.x
99
This version introduces support for grouping jobs into batches, which needs new tables. Fresh installs get them with the base schema; existing installations need to copy the migration that adds them and run it:

‎test/models/solid_queue/semaphore_test.rb‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,29 @@ class SolidQueue::SemaphoreTest < ActiveSupport::TestCase
99
@result = JobResult.create!(queue_name: "default")
1010
end
1111

12+
test "a job records the semaphore it created, not one deleted after its transaction began" do
13+
skip "Only MySQL reads a deleted row from the transaction's snapshot" unless SolidQueue::Record.connection.adapter_name.downcase.match?(/mysql|trilogy/)
14+
15+
NonOverlappingUpdateResultJob.perform_later(@result)
16+
expired = SolidQueue::Semaphore.sole
17+
18+
SolidQueue::Record.transaction do
19+
# Take the transaction's snapshot, as dispatching does when it reads its batch of jobs
20+
SolidQueue::Job.count
21+
22+
# Maintenance deletes the expired semaphore from another connection
23+
Thread.new do
24+
SolidQueue::Record.connection_pool.with_connection { SolidQueue::Semaphore.where(id: expired.id).delete_all }
25+
end.join
26+
27+
job = SolidQueue::Job.enqueue(NonOverlappingUpdateResultJob.new(@result))
28+
29+
assert job.ready?
30+
assert_not_equal expired.id, job.concurrency_lock["semaphore_id"]
31+
assert_equal SolidQueue::Semaphore.lock.find_by!(key: job.concurrency_key).id, job.concurrency_lock["semaphore_id"]
32+
end
33+
end
34+
1235
test "wait acquires a row lock that blocks concurrent signal" do
1336
skip_on_sqlite
1437

0 commit comments

Comments
 (0)