|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require "test_helper" |
| 4 | + |
| 5 | +class ConcurrencyDiscardTest < ActiveSupport::TestCase |
| 6 | + setup do |
| 7 | + @job_result = JobResult.create!(queue_name: "default", status: "test") |
| 8 | + end |
| 9 | + |
| 10 | + test "discard jobs when concurrency limit is reached with on_conflict: :discard" do |
| 11 | + # Enqueue first job - should be executed |
| 12 | + job1 = DiscardOnConflictJob.perform_later(@job_result.id) |
| 13 | + |
| 14 | + # Enqueue second job - should be discarded due to concurrency limit |
| 15 | + job2 = DiscardOnConflictJob.perform_later(@job_result.id) |
| 16 | + |
| 17 | + # Enqueue third job - should also be discarded |
| 18 | + job3 = DiscardOnConflictJob.perform_later(@job_result.id) |
| 19 | + |
| 20 | + # Check that first job was ready |
| 21 | + solid_job1 = SolidQueue::Job.find_by(active_job_id: job1.job_id) |
| 22 | + assert solid_job1.ready? |
| 23 | + assert solid_job1.ready_execution.present? |
| 24 | + |
| 25 | + # Check that second and third jobs were discarded |
| 26 | + solid_job2 = SolidQueue::Job.find_by(active_job_id: job2.job_id) |
| 27 | + assert solid_job2.finished? |
| 28 | + assert_nil solid_job2.ready_execution |
| 29 | + assert_nil solid_job2.blocked_execution |
| 30 | + |
| 31 | + solid_job3 = SolidQueue::Job.find_by(active_job_id: job3.job_id) |
| 32 | + assert solid_job3.finished? |
| 33 | + assert_nil solid_job3.ready_execution |
| 34 | + assert_nil solid_job3.blocked_execution |
| 35 | + end |
| 36 | + |
| 37 | + test "block jobs when concurrency limit is reached without on_conflict option" do |
| 38 | + # Using SequentialUpdateResultJob which has default blocking behavior |
| 39 | + # Enqueue first job - should be executed |
| 40 | + job1 = SequentialUpdateResultJob.perform_later(@job_result, name: "A") |
| 41 | + |
| 42 | + # Enqueue second job - should be blocked due to concurrency limit |
| 43 | + job2 = SequentialUpdateResultJob.perform_later(@job_result, name: "B") |
| 44 | + |
| 45 | + # Check that second job is blocked |
| 46 | + solid_job2 = SolidQueue::Job.find_by(active_job_id: job2.job_id) |
| 47 | + assert solid_job2.blocked? |
| 48 | + assert solid_job2.blocked_execution.present? |
| 49 | + end |
| 50 | + |
| 51 | + test "respect concurrency limit with discard option" do |
| 52 | + # Enqueue jobs with limit of 2 |
| 53 | + job1 = LimitedDiscardJob.perform_later("group1", 1) |
| 54 | + job2 = LimitedDiscardJob.perform_later("group1", 2) |
| 55 | + job3 = LimitedDiscardJob.perform_later("group1", 3) # Should be discarded |
| 56 | + job4 = LimitedDiscardJob.perform_later("group1", 4) # Should be discarded |
| 57 | + |
| 58 | + # Check that first two jobs are ready |
| 59 | + solid_job1 = SolidQueue::Job.find_by(active_job_id: job1.job_id) |
| 60 | + solid_job2 = SolidQueue::Job.find_by(active_job_id: job2.job_id) |
| 61 | + assert solid_job1.ready? |
| 62 | + assert solid_job2.ready? |
| 63 | + |
| 64 | + # Check that third and fourth jobs are discarded |
| 65 | + solid_job3 = SolidQueue::Job.find_by(active_job_id: job3.job_id) |
| 66 | + solid_job4 = SolidQueue::Job.find_by(active_job_id: job4.job_id) |
| 67 | + assert solid_job3.finished? |
| 68 | + assert solid_job4.finished? |
| 69 | + assert_nil solid_job3.ready_execution |
| 70 | + assert_nil solid_job4.ready_execution |
| 71 | + end |
| 72 | + |
| 73 | + test "discard option works with different concurrency keys" do |
| 74 | + # These should not conflict because they have different keys |
| 75 | + job1 = DiscardOnConflictJob.perform_later("key1") |
| 76 | + job2 = DiscardOnConflictJob.perform_later("key2") |
| 77 | + job3 = DiscardOnConflictJob.perform_later("key1") # Should be discarded |
| 78 | + |
| 79 | + # Check that first two jobs are ready (different keys) |
| 80 | + solid_job1 = SolidQueue::Job.find_by(active_job_id: job1.job_id) |
| 81 | + solid_job2 = SolidQueue::Job.find_by(active_job_id: job2.job_id) |
| 82 | + assert solid_job1.ready? |
| 83 | + assert solid_job2.ready? |
| 84 | + |
| 85 | + # Check that third job is discarded (same key as first) |
| 86 | + solid_job3 = SolidQueue::Job.find_by(active_job_id: job3.job_id) |
| 87 | + assert solid_job3.finished? |
| 88 | + assert_nil solid_job3.ready_execution |
| 89 | + end |
| 90 | + |
| 91 | + test "discarded jobs do not unblock other jobs" do |
| 92 | + # Enqueue a job that will be executed |
| 93 | + job1 = DiscardOnConflictJob.perform_later(@job_result.id) |
| 94 | + |
| 95 | + # Enqueue a job that will be discarded |
| 96 | + job2 = DiscardOnConflictJob.perform_later(@job_result.id) |
| 97 | + |
| 98 | + # The first job should be ready |
| 99 | + solid_job1 = SolidQueue::Job.find_by(active_job_id: job1.job_id) |
| 100 | + assert solid_job1.ready? |
| 101 | + |
| 102 | + # The second job should be discarded immediately |
| 103 | + solid_job2 = SolidQueue::Job.find_by(active_job_id: job2.job_id) |
| 104 | + assert solid_job2.finished? |
| 105 | + |
| 106 | + # Complete the first job and release its lock |
| 107 | + solid_job1.unblock_next_blocked_job |
| 108 | + solid_job1.finished! |
| 109 | + |
| 110 | + # Enqueue another job - it should be ready since the lock is released |
| 111 | + job3 = DiscardOnConflictJob.perform_later(@job_result.id) |
| 112 | + solid_job3 = SolidQueue::Job.find_by(active_job_id: job3.job_id) |
| 113 | + assert solid_job3.ready? |
| 114 | + end |
| 115 | + |
| 116 | + test "discarded jobs are marked as finished without execution" do |
| 117 | + # Enqueue a job that will be ready |
| 118 | + job1 = DiscardOnConflictJob.perform_later("test_key") |
| 119 | + |
| 120 | + # Enqueue a job that will be discarded |
| 121 | + job2 = DiscardOnConflictJob.perform_later("test_key") |
| 122 | + |
| 123 | + solid_job1 = SolidQueue::Job.find_by(active_job_id: job1.job_id) |
| 124 | + solid_job2 = SolidQueue::Job.find_by(active_job_id: job2.job_id) |
| 125 | + |
| 126 | + # First job should be ready |
| 127 | + assert solid_job1.ready? |
| 128 | + assert solid_job1.ready_execution.present? |
| 129 | + |
| 130 | + # Second job should be finished without any execution |
| 131 | + assert solid_job2.finished? |
| 132 | + assert_nil solid_job2.ready_execution |
| 133 | + assert_nil solid_job2.claimed_execution |
| 134 | + assert_nil solid_job2.failed_execution |
| 135 | + assert_nil solid_job2.blocked_execution |
| 136 | + end |
| 137 | +end |
0 commit comments