From c2e5d68252b098821e96d442b4948360d1e00d38 Mon Sep 17 00:00:00 2001 From: wintan1418 Date: Thu, 30 Jul 2026 11:51:37 +0100 Subject: [PATCH] Reschedule dynamic recurring tasks when they are updated The scheduler's polling cycle only handled created and deleted dynamic tasks. A task whose schedule, class, arguments or any other attribute changed fell through both checks, so the scheduler kept firing on the old definition until the process was restarted. Keep the previously loaded snapshot of dynamic tasks and compare each reloaded task's updated_at against it, cancelling and rescheduling any task that changed. Fixes rails/solid_queue#738 --- .../scheduler/recurring_schedule.rb | 12 +++++++++++ test/unit/scheduler_test.rb | 20 +++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/solid_queue/scheduler/recurring_schedule.rb b/lib/solid_queue/scheduler/recurring_schedule.rb index 526a8edc..36cdc231 100644 --- a/lib/solid_queue/scheduler/recurring_schedule.rb +++ b/lib/solid_queue/scheduler/recurring_schedule.rb @@ -48,8 +48,10 @@ def task_keys def reschedule_dynamic_tasks wrap_in_app_executor do + previous_tasks = dynamic_tasks.index_by(&:key) reload_dynamic_tasks schedule_created_dynamic_tasks + reschedule_updated_dynamic_tasks(previous_tasks) unschedule_deleted_dynamic_tasks end end @@ -75,6 +77,16 @@ def schedule_created_dynamic_tasks end end + def reschedule_updated_dynamic_tasks(previous_tasks) + dynamic_tasks.each do |task| + previous_task = previous_tasks[task.key] + next if previous_task.nil? || previous_task.updated_at == task.updated_at + + scheduled_tasks[task.key]&.cancel + schedule_task(task) + end + end + def unschedule_deleted_dynamic_tasks (scheduled_tasks.keys - RecurringTask.pluck(:key)).each do |key| scheduled_tasks[key].cancel diff --git a/test/unit/scheduler_test.rb b/test/unit/scheduler_test.rb index e914a23c..241501e9 100644 --- a/test/unit/scheduler_test.rb +++ b/test/unit/scheduler_test.rb @@ -131,6 +131,26 @@ class SchedulerTest < ActiveSupport::TestCase scheduler&.stop end + test "picks up updates to dynamic tasks post-start" do + task = SolidQueue::RecurringTask.create!( + key: "updatable_task", static: false, class_name: "AddToBufferJob", schedule: "every hour", arguments: [ 42 ] + ) + + scheduler = SolidQueue::Scheduler.new(recurring_tasks: {}, dynamic_tasks_enabled: true, polling_interval: 0.1).tap(&:start) + + wait_for_registered_processes(1, timeout: 1.second) + + task.update!(schedule: "every second") + + wait_while_with_timeout(3.seconds) { SolidQueue::Job.count < 1 } + + skip_active_record_query_cache do + assert SolidQueue::Job.count >= 1, "Expected the updated schedule to enqueue jobs without a scheduler restart" + end + ensure + scheduler&.stop + end + test "updates metadata after removing dynamic task post-start" do old_dynamic_task = SolidQueue::RecurringTask.create!( key: "old_dynamic_task",