aboutsummaryrefslogtreecommitdiffstats
path: root/activejob/lib/active_job/exceptions.rb
blob: eead8daae3ab488705c14721143d6baacab4479f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
require 'active_support/core_ext/numeric/time'

module ActiveJob
  # Provides behavior for retrying and discarding jobs on exceptions.
  module Exceptions
    extend ActiveSupport::Concern

    module ClassMethods
      # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
      # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
      # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a
      # holding queue for inspection.
      #
      # ==== Options
      # * <tt>:wait</tt> - Re-enqueues the job with the specified delay in seconds (default: 3 seconds)
      # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
      # * <tt>:queue</tt> - Re-enqueues the job on a different queue
      # * <tt>:priority</tt> - Re-enqueues the job with a different priority
      #
      # ==== Examples
      #
      #  class RemoteServiceJob < ActiveJob::Base
      #    retry_on Net::OpenTimeout, wait: 30.seconds, attempts: 10
      #
      #    def perform(*args)
      #      # Might raise Net::OpenTimeout when the remote service is down
      #    end
      #  end
      def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
        rescue_from exception do |error|
          if executions < attempts
            logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}."
            retry_job wait: wait, queue: queue, priority: priority
          else
            logger.error "Discarded #{self.class} due to a #{exception} which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
          end
        end
      end

      # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job,
      # like an Active Record, is no longer available, and the job is thus no longer relevant.
      #
      # ==== Example
      #
      #  class SearchIndexingJob < ActiveJob::Base
      #    discard_on ActiveJob::DeserializationError
      #
      #    def perform(record)
      #      # Will raise ActiveJob::DeserializationError if the record can't be deserialized
      #    end
      #  end
      def discard_on(exception)
        rescue_from exception do |error|
          logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
        end
      end
    end

    # Reschedules the job to be re-executed. This is useful in combination
    # with the +rescue_from+ option. When you rescue an exception from your job
    # you can ask Active Job to retry performing your job.
    #
    # ==== Options
    # * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds
    # * <tt>:wait_until</tt> - Enqueues the job at the time specified
    # * <tt>:queue</tt> - Enqueues the job on the specified queue
    # * <tt>:priority</tt> - Enqueues the job with the specified priority
    #
    # ==== Examples
    #
    #  class SiteScraperJob < ActiveJob::Base
    #    rescue_from(ErrorLoadingSite) do
    #      retry_job queue: :low_priority
    #    end
    #
    #    def perform(*args)
    #      # raise ErrorLoadingSite if cannot scrape
    #    end
    #  end
    def retry_job(options = {})
      enqueue options
    end
  end
end