@@ -80,7 +80,7 @@ class ConcurrencyControlsTest < ActiveSupport::TestCase
80
80
assert_stored_sequence @result , [ "B" , "D" , "F" ] + ( "G" .."K" ) . to_a
81
81
end
82
82
83
- test "rely on worker to unblock blocked executions" do
83
+ test "rely on worker to unblock blocked executions with an available semaphore " do
84
84
# Simulate a scenario where we got an available semaphore and some stuck jobs
85
85
job = SequentialUpdateResultJob . perform_later ( @result , name : "A" )
86
86
wait_for_jobs_to_finish_for ( 2 . seconds )
@@ -94,26 +94,59 @@ class ConcurrencyControlsTest < ActiveSupport::TestCase
94
94
# Now enqueue more jobs under that same key. They'll be all locked. Use priorities
95
95
# to ensure order.
96
96
assert_difference -> { SolidQueue ::BlockedExecution . count } , +10 do
97
- ( "B" .."K" ) . each_with_index do |name , i |
98
- SequentialUpdateResultJob . set ( priority : i ) . perform_later ( @result , name : name )
97
+ ( "B" .."K" ) . each do |name |
98
+ SequentialUpdateResultJob . perform_later ( @result , name : name )
99
99
end
100
100
end
101
101
102
102
# Then unlock the semaphore: this would be as if the first job had released
103
103
# the semaphore but hadn't unblocked any jobs
104
104
assert SolidQueue ::Semaphore . release ( job . concurrency_key , job . concurrency_limit )
105
105
106
+ # And wait for workers to release the jobs
106
107
wait_for_jobs_to_finish_for ( 2 . seconds )
107
108
assert_no_pending_jobs
108
109
109
- assert_stored_sequence @result , ( "A" .."K" ) . to_a
110
+ # We can't ensure the order between B and C, because it depends on which worker wins when
111
+ # unblocking, as one will try to unblock B and another C
112
+ assert_stored_sequence @result , ( "A" .."K" ) . to_a , [ "A" , "C" , "B" ] + ( "D" .."K" ) . to_a
113
+ end
114
+
115
+ test "rely on worker to unblock blocked executions with a missing semaphore" do
116
+ # Simulate a scenario where we got an available semaphore and some stuck jobs
117
+ job = SequentialUpdateResultJob . perform_later ( @result , name : "A" )
118
+ wait_for_jobs_to_finish_for ( 2 . seconds )
119
+ assert_no_pending_jobs
120
+
121
+ # Lock the semaphore so we can enqueue jobs and leave them blocked
122
+ skip_active_record_query_cache do
123
+ assert SolidQueue ::Semaphore . wait_for ( job . concurrency_key , job . concurrency_limit )
124
+ end
125
+
126
+ # Now enqueue more jobs under that same key. They'll be all locked
127
+ assert_difference -> { SolidQueue ::BlockedExecution . count } , +10 do
128
+ ( "B" .."K" ) . each do |name |
129
+ SequentialUpdateResultJob . perform_later ( @result , name : name )
130
+ end
131
+ end
132
+
133
+ # Then delete the semaphore, as if we had cleared it
134
+ SolidQueue ::Semaphore . find_by ( identifier : job . concurrency_key ) . destroy!
135
+
136
+ # And wait for workers to release the jobs
137
+ wait_for_jobs_to_finish_for ( 2 . seconds )
138
+ assert_no_pending_jobs
139
+
140
+ # We can't ensure the order between B and C, because it depends on which worker wins when
141
+ # unblocking, as one will try to unblock B and another C
142
+ assert_stored_sequence @result , ( "A" .."K" ) . to_a , [ "A" , "C" , "B" ] + ( "D" .."K" ) . to_a
110
143
end
111
144
112
145
private
113
- def assert_stored_sequence ( result , sequence )
114
- expected = "seq: " + sequence . map { |name | "s#{ name } c#{ name } " } . join
146
+ def assert_stored_sequence ( result , * sequences )
147
+ expected = sequences . map { | sequence | "seq: " + sequence . map { |name | "s#{ name } c#{ name } " } . join }
115
148
skip_active_record_query_cache do
116
- assert_equal expected , result . reload . status
149
+ assert_includes expected , result . reload . status
117
150
end
118
151
end
119
152
end
0 commit comments