Skip to content

Commit 7fce00a

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 2645bfb commit 7fce00a

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
@@ -16,6 +16,29 @@ def perform(key)
1616
@result = JobResult.create!(queue_name: "default")
1717
end
1818

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

0 commit comments

Comments
 (0)