From 4929bbb957e1a2402593865cc364ec4d4fa38c4f Mon Sep 17 00:00:00 2001 From: Pissardo Date: Wed, 22 Jul 2026 15:46:05 +0200 Subject: [PATCH] Report rescued recurring enqueue errors to Rails.error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RecurringTask#enqueue rescues Job::EnqueueError (and other-adapter enqueue failures) but forwarded only the message string to the notification payload, never calling Rails.error.report. Reporters subscribed via ActiveSupport::ErrorReporter (Sentry, etc.) therefore never saw recurring enqueue failures — only a log line — so DB write failures during recurring enqueue produced missed ticks with no alerting. Report the rescued exception via Rails.error.report(handled: true) from both the Solid Queue and other-adapter paths, keeping the non-bubbling behavior and the notification payload. This aligns the recurring path with the gem's default on_thread_error reporting. Fixes #746. --- app/models/solid_queue/recurring_task.rb | 8 ++++ .../models/solid_queue/recurring_task_test.rb | 47 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/app/models/solid_queue/recurring_task.rb b/app/models/solid_queue/recurring_task.rb index be12d46b..40a5531e 100644 --- a/app/models/solid_queue/recurring_task.rb +++ b/app/models/solid_queue/recurring_task.rb @@ -86,6 +86,7 @@ def enqueue(at:) perform_later.tap do |job| unless job.successfully_enqueued? + report_enqueue_error(job.enqueue_error, at: at) payload[:enqueue_error] = job.enqueue_error&.message end end @@ -98,6 +99,7 @@ def enqueue(at:) payload[:skipped] = true false rescue Job::EnqueueError => error + report_enqueue_error(error, at: at) payload[:enqueue_error] = error.message false end @@ -198,5 +200,11 @@ def enqueue_options def default_time_zone SolidQueue.time_zone end + + def report_enqueue_error(error, at:) + if error + Rails.error.report(error, handled: true, source: "application.solid_queue", context: { task: key, at: at }) + end + end end end diff --git a/test/models/solid_queue/recurring_task_test.rb b/test/models/solid_queue/recurring_task_test.rb index 20bb50d8..518fc7ed 100644 --- a/test/models/solid_queue/recurring_task_test.rb +++ b/test/models/solid_queue/recurring_task_test.rb @@ -307,6 +307,46 @@ def perform end end + test "reports Job::EnqueueError to Rails.error when enqueuing via Solid Queue" do + SolidQueue::Job.stubs(:create!).raises(ActiveRecord::Deadlocked) + subscriber = ErrorBuffer.new + at = Time.now + + with_error_subscriber(subscriber) do + task = recurring_task_with(class_name: "JobWithoutArguments") + task.enqueue(at: at) + end + + assert_equal 1, subscriber.errors.count + error, options = subscriber.errors.first + assert_kind_of SolidQueue::Job::EnqueueError, error + assert_match "ActiveRecord::Deadlocked", error.message + assert_equal true, options[:handled] + assert_equal "application.solid_queue", options[:source] + assert_equal "task-id", options[:context][:task] + assert_equal at, options[:context][:at] + end + + test "reports enqueue error to Rails.error when using another adapter" do + ActiveJob::QueueAdapters::AsyncAdapter.any_instance.stubs(:enqueue).raises(ActiveJob::EnqueueError.new("All is broken")) + subscriber = ErrorBuffer.new + at = Time.now + + with_error_subscriber(subscriber) do + task = recurring_task_with(class_name: "JobUsingAsyncAdapter") + task.enqueue(at: at) + end + + assert_equal 1, subscriber.errors.count + error, options = subscriber.errors.first + assert_kind_of ActiveJob::EnqueueError, error + assert_equal "All is broken", error.message + assert_equal true, options[:handled] + assert_equal "application.solid_queue", options[:source] + assert_equal "task-id", options[:context][:task] + assert_equal at, options[:context][:at] + end + private def with_time_zone(zone) previous = SolidQueue.time_zone @@ -344,4 +384,11 @@ def run_all_jobs_inline worker.start end end + + def with_error_subscriber(subscriber) + Rails.error.subscribe(subscriber) + yield + ensure + Rails.error.unsubscribe(subscriber) if Rails.error.respond_to?(:unsubscribe) + end end