activejob 5.0.7.2 → 5.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activejob might be problematic. Click here for more details.

Files changed (37) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +24 -221
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +1 -1
  5. data/lib/active_job.rb +5 -5
  6. data/lib/active_job/arguments.rb +7 -19
  7. data/lib/active_job/base.rb +11 -9
  8. data/lib/active_job/callbacks.rb +3 -3
  9. data/lib/active_job/configured_job.rb +1 -1
  10. data/lib/active_job/core.rb +14 -9
  11. data/lib/active_job/enqueuing.rb +7 -32
  12. data/lib/active_job/exceptions.rb +122 -0
  13. data/lib/active_job/execution.rb +5 -2
  14. data/lib/active_job/gem_version.rb +3 -3
  15. data/lib/active_job/logging.rb +56 -56
  16. data/lib/active_job/queue_adapter.rb +14 -26
  17. data/lib/active_job/queue_adapters.rb +2 -2
  18. data/lib/active_job/queue_adapters/async_adapter.rb +4 -4
  19. data/lib/active_job/queue_adapters/backburner_adapter.rb +1 -1
  20. data/lib/active_job/queue_adapters/delayed_job_adapter.rb +1 -1
  21. data/lib/active_job/queue_adapters/qu_adapter.rb +3 -3
  22. data/lib/active_job/queue_adapters/que_adapter.rb +1 -1
  23. data/lib/active_job/queue_adapters/queue_classic_adapter.rb +4 -4
  24. data/lib/active_job/queue_adapters/resque_adapter.rb +5 -5
  25. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +11 -11
  26. data/lib/active_job/queue_adapters/sneakers_adapter.rb +3 -3
  27. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +3 -3
  28. data/lib/active_job/queue_adapters/test_adapter.rb +13 -17
  29. data/lib/active_job/queue_name.rb +2 -3
  30. data/lib/active_job/queue_priority.rb +1 -2
  31. data/lib/active_job/railtie.rb +4 -4
  32. data/lib/active_job/test_case.rb +1 -1
  33. data/lib/active_job/test_helper.rb +78 -39
  34. data/lib/active_job/version.rb +1 -1
  35. data/lib/rails/generators/job/job_generator.rb +12 -12
  36. data/lib/rails/generators/job/templates/application_job.rb +5 -0
  37. metadata +9 -7
@@ -1,7 +1,7 @@
1
- require 'active_job/arguments'
1
+ require "active_job/arguments"
2
2
 
3
3
  module ActiveJob
4
- # Provides behavior for enqueuing and retrying jobs.
4
+ # Provides behavior for enqueuing jobs.
5
5
  module Enqueuing
6
6
  extend ActiveSupport::Concern
7
7
 
@@ -18,37 +18,12 @@ module ActiveJob
18
18
  job_or_instantiate(*args).enqueue
19
19
  end
20
20
 
21
- protected
22
- def job_or_instantiate(*args)
21
+ private
22
+ def job_or_instantiate(*args) # :doc:
23
23
  args.first.is_a?(self) ? args.first : new(*args)
24
24
  end
25
25
  end
26
26
 
27
- # Reschedules the job to be re-executed. This is useful in combination
28
- # with the +rescue_from+ option. When you rescue an exception from your job
29
- # you can ask Active Job to retry performing your job.
30
- #
31
- # ==== Options
32
- # * <tt>:wait</tt> - Enqueues the job with the specified delay
33
- # * <tt>:wait_until</tt> - Enqueues the job at the time specified
34
- # * <tt>:queue</tt> - Enqueues the job on the specified queue
35
- # * <tt>:priority</tt> - Enqueues the job with the specified priority
36
- #
37
- # ==== Examples
38
- #
39
- # class SiteScraperJob < ActiveJob::Base
40
- # rescue_from(ErrorLoadingSite) do
41
- # retry_job queue: :low_priority
42
- # end
43
- #
44
- # def perform(*args)
45
- # # raise ErrorLoadingSite if cannot scrape
46
- # end
47
- # end
48
- def retry_job(options={})
49
- enqueue options
50
- end
51
-
52
27
  # Enqueues the job to be performed by the queue adapter.
53
28
  #
54
29
  # ==== Options
@@ -64,14 +39,14 @@ module ActiveJob
64
39
  # my_job_instance.enqueue queue: :important
65
40
  # my_job_instance.enqueue wait_until: Date.tomorrow.midnight
66
41
  # my_job_instance.enqueue priority: 10
67
- def enqueue(options={})
42
+ def enqueue(options = {})
68
43
  self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait]
69
44
  self.scheduled_at = options[:wait_until].to_f if options[:wait_until]
70
45
  self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue]
71
46
  self.priority = options[:priority].to_i if options[:priority]
72
47
  run_callbacks :enqueue do
73
- if self.scheduled_at
74
- self.class.queue_adapter.enqueue_at self, self.scheduled_at
48
+ if scheduled_at
49
+ self.class.queue_adapter.enqueue_at self, scheduled_at
75
50
  else
76
51
  self.class.queue_adapter.enqueue self
77
52
  end
@@ -0,0 +1,122 @@
1
+ require "active_support/core_ext/numeric/time"
2
+
3
+ module ActiveJob
4
+ # Provides behavior for retrying and discarding jobs on exceptions.
5
+ module Exceptions
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ # Catch the exception and reschedule job for re-execution after so many seconds, for a specific number of attempts.
10
+ # If the exception keeps getting raised beyond the specified number of attempts, the exception is allowed to
11
+ # bubble up to the underlying queuing system, which may have its own retry mechanism or place it in a
12
+ # holding queue for inspection.
13
+ #
14
+ # You can also pass a block that'll be invoked if the retry attempts fail for custom logic rather than letting
15
+ # the exception bubble up. This block is yielded with the job instance as the first and the error instance as the second parameter.
16
+ #
17
+ # ==== Options
18
+ # * <tt>:wait</tt> - Re-enqueues the job with a delay specified either in seconds (default: 3 seconds),
19
+ # as a computing proc that the number of executions so far as an argument, or as a symbol reference of
20
+ # <tt>:exponentially_longer</tt>, which applies the wait algorithm of <tt>(executions ** 4) + 2</tt>
21
+ # (first wait 3s, then 18s, then 83s, etc)
22
+ # * <tt>:attempts</tt> - Re-enqueues the job the specified number of times (default: 5 attempts)
23
+ # * <tt>:queue</tt> - Re-enqueues the job on a different queue
24
+ # * <tt>:priority</tt> - Re-enqueues the job with a different priority
25
+ #
26
+ # ==== Examples
27
+ #
28
+ # class RemoteServiceJob < ActiveJob::Base
29
+ # retry_on CustomAppException # defaults to 3s wait, 5 attempts
30
+ # retry_on AnotherCustomAppException, wait: ->(executions) { executions * 2 }
31
+ # retry_on(YetAnotherCustomAppException) do |job, exception|
32
+ # ExceptionNotifier.caught(exception)
33
+ # end
34
+ # retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
35
+ # retry_on Net::OpenTimeout, wait: :exponentially_longer, attempts: 10
36
+ #
37
+ # def perform(*args)
38
+ # # Might raise CustomAppException, AnotherCustomAppException, or YetAnotherCustomAppException for something domain specific
39
+ # # Might raise ActiveRecord::Deadlocked when a local db deadlock is detected
40
+ # # Might raise Net::OpenTimeout when the remote service is down
41
+ # end
42
+ # end
43
+ def retry_on(exception, wait: 3.seconds, attempts: 5, queue: nil, priority: nil)
44
+ rescue_from exception do |error|
45
+ if executions < attempts
46
+ logger.error "Retrying #{self.class} in #{wait} seconds, due to a #{exception}. The original exception was #{error.cause.inspect}."
47
+ retry_job wait: determine_delay(wait), queue: queue, priority: priority
48
+ else
49
+ if block_given?
50
+ yield self, exception
51
+ else
52
+ logger.error "Stopped retrying #{self.class} due to a #{exception}, which reoccurred on #{executions} attempts. The original exception was #{error.cause.inspect}."
53
+ raise error
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # Discard the job with no attempts to retry, if the exception is raised. This is useful when the subject of the job,
60
+ # like an Active Record, is no longer available, and the job is thus no longer relevant.
61
+ #
62
+ # ==== Example
63
+ #
64
+ # class SearchIndexingJob < ActiveJob::Base
65
+ # discard_on ActiveJob::DeserializationError
66
+ #
67
+ # def perform(record)
68
+ # # Will raise ActiveJob::DeserializationError if the record can't be deserialized
69
+ # end
70
+ # end
71
+ def discard_on(exception)
72
+ rescue_from exception do |error|
73
+ logger.error "Discarded #{self.class} due to a #{exception}. The original exception was #{error.cause.inspect}."
74
+ end
75
+ end
76
+ end
77
+
78
+ # Reschedules the job to be re-executed. This is useful in combination
79
+ # with the +rescue_from+ option. When you rescue an exception from your job
80
+ # you can ask Active Job to retry performing your job.
81
+ #
82
+ # ==== Options
83
+ # * <tt>:wait</tt> - Enqueues the job with the specified delay in seconds
84
+ # * <tt>:wait_until</tt> - Enqueues the job at the time specified
85
+ # * <tt>:queue</tt> - Enqueues the job on the specified queue
86
+ # * <tt>:priority</tt> - Enqueues the job with the specified priority
87
+ #
88
+ # ==== Examples
89
+ #
90
+ # class SiteScraperJob < ActiveJob::Base
91
+ # rescue_from(ErrorLoadingSite) do
92
+ # retry_job queue: :low_priority
93
+ # end
94
+ #
95
+ # def perform(*args)
96
+ # # raise ErrorLoadingSite if cannot scrape
97
+ # end
98
+ # end
99
+ def retry_job(options = {})
100
+ enqueue options
101
+ end
102
+
103
+ private
104
+ def determine_delay(seconds_or_duration_or_algorithm)
105
+ case seconds_or_duration_or_algorithm
106
+ when :exponentially_longer
107
+ (executions**4) + 2
108
+ when ActiveSupport::Duration
109
+ duration = seconds_or_duration_or_algorithm
110
+ duration.to_i
111
+ when Integer
112
+ seconds = seconds_or_duration_or_algorithm
113
+ seconds
114
+ when Proc
115
+ algorithm = seconds_or_duration_or_algorithm
116
+ algorithm.call(executions)
117
+ else
118
+ raise "Couldn't determine a delay based on #{seconds_or_duration_or_algorithm.inspect}"
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,5 +1,5 @@
1
- require 'active_support/rescuable'
2
- require 'active_job/arguments'
1
+ require "active_support/rescuable"
2
+ require "active_job/arguments"
3
3
 
4
4
  module ActiveJob
5
5
  module Execution
@@ -31,6 +31,9 @@ module ActiveJob
31
31
  def perform_now
32
32
  deserialize_arguments_if_needed
33
33
  run_callbacks :perform do
34
+ # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
35
+ self.executions = (executions || 0) + 1
36
+
34
37
  perform(*arguments)
35
38
  end
36
39
  rescue => exception
@@ -6,9 +6,9 @@ module ActiveJob
6
6
 
7
7
  module VERSION
8
8
  MAJOR = 5
9
- MINOR = 0
10
- TINY = 7
11
- PRE = "2"
9
+ MINOR = 1
10
+ TINY = 0
11
+ PRE = "beta1"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end
@@ -1,7 +1,7 @@
1
- require 'active_support/core_ext/hash/transform_values'
2
- require 'active_support/core_ext/string/filters'
3
- require 'active_support/tagged_logging'
4
- require 'active_support/logger'
1
+ require "active_support/core_ext/hash/transform_values"
2
+ require "active_support/core_ext/string/filters"
3
+ require "active_support/tagged_logging"
4
+ require "active_support/logger"
5
5
 
6
6
  module ActiveJob
7
7
  module Logging #:nodoc:
@@ -18,7 +18,7 @@ module ActiveJob
18
18
 
19
19
  around_perform do |job, block, _|
20
20
  tag_logger(job.class.name, job.job_id) do
21
- payload = {adapter: job.class.queue_adapter, job: job}
21
+ payload = { adapter: job.class.queue_adapter, job: job }
22
22
  ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
23
23
  ActiveSupport::Notifications.instrument("perform.active_job", payload) do
24
24
  block.call
@@ -41,7 +41,7 @@ module ActiveJob
41
41
  def tag_logger(*tags)
42
42
  if logger.respond_to?(:tagged)
43
43
  tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
44
- logger.tagged(*tags){ yield }
44
+ logger.tagged(*tags) { yield }
45
45
  else
46
46
  yield
47
47
  end
@@ -51,70 +51,70 @@ module ActiveJob
51
51
  logger.formatter.current_tags.include?("ActiveJob")
52
52
  end
53
53
 
54
- class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
55
- def enqueue(event)
56
- info do
57
- job = event.payload[:job]
58
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
54
+ class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
55
+ def enqueue(event)
56
+ info do
57
+ job = event.payload[:job]
58
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
59
+ end
59
60
  end
60
- end
61
61
 
62
- def enqueue_at(event)
63
- info do
64
- job = event.payload[:job]
65
- "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
62
+ def enqueue_at(event)
63
+ info do
64
+ job = event.payload[:job]
65
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
66
+ end
66
67
  end
67
- end
68
68
 
69
- def perform_start(event)
70
- info do
71
- job = event.payload[:job]
72
- "Performing #{job.class.name} from #{queue_name(event)}" + args_info(job)
69
+ def perform_start(event)
70
+ info do
71
+ job = event.payload[:job]
72
+ "Performing #{job.class.name} from #{queue_name(event)}" + args_info(job)
73
+ end
73
74
  end
74
- end
75
75
 
76
- def perform(event)
77
- info do
78
- job = event.payload[:job]
79
- "Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms"
76
+ def perform(event)
77
+ info do
78
+ job = event.payload[:job]
79
+ "Performed #{job.class.name} from #{queue_name(event)} in #{event.duration.round(2)}ms"
80
+ end
80
81
  end
81
- end
82
82
 
83
- private
84
- def queue_name(event)
85
- event.payload[:adapter].class.name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
86
- end
83
+ private
84
+ def queue_name(event)
85
+ event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
86
+ end
87
87
 
88
- def args_info(job)
89
- if job.arguments.any?
90
- ' with arguments: ' +
91
- job.arguments.map { |arg| format(arg).inspect }.join(', ')
92
- else
93
- ''
88
+ def args_info(job)
89
+ if job.arguments.any?
90
+ " with arguments: " +
91
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
92
+ else
93
+ ""
94
+ end
94
95
  end
95
- end
96
96
 
97
- def format(arg)
98
- case arg
99
- when Hash
100
- arg.transform_values { |value| format(value) }
101
- when Array
102
- arg.map { |value| format(value) }
103
- when GlobalID::Identification
104
- arg.to_global_id rescue arg
105
- else
106
- arg
97
+ def format(arg)
98
+ case arg
99
+ when Hash
100
+ arg.transform_values { |value| format(value) }
101
+ when Array
102
+ arg.map { |value| format(value) }
103
+ when GlobalID::Identification
104
+ arg.to_global_id rescue arg
105
+ else
106
+ arg
107
+ end
107
108
  end
108
- end
109
109
 
110
- def scheduled_at(event)
111
- Time.at(event.payload[:job].scheduled_at).utc
112
- end
110
+ def scheduled_at(event)
111
+ Time.at(event.payload[:job].scheduled_at).utc
112
+ end
113
113
 
114
- def logger
115
- ActiveJob::Base.logger
116
- end
117
- end
114
+ def logger
115
+ ActiveJob::Base.logger
116
+ end
117
+ end
118
118
  end
119
119
  end
120
120
 
@@ -1,6 +1,4 @@
1
- require 'active_job/queue_adapters/inline_adapter'
2
- require 'active_support/core_ext/class/attribute'
3
- require 'active_support/core_ext/string/inflections'
1
+ require "active_support/core_ext/string/inflections"
4
2
 
5
3
  module ActiveJob
6
4
  # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
@@ -30,34 +28,24 @@ module ActiveJob
30
28
 
31
29
  private
32
30
 
33
- def interpret_adapter(name_or_adapter_or_class)
34
- case name_or_adapter_or_class
35
- when Symbol, String
36
- ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new
37
- else
38
- if queue_adapter?(name_or_adapter_or_class)
39
- name_or_adapter_or_class
40
- elsif queue_adapter_class?(name_or_adapter_or_class)
41
- ActiveSupport::Deprecation.warn "Passing an adapter class is deprecated " \
42
- "and will be removed in Rails 5.1. Please pass an adapter name " \
43
- "(.queue_adapter = :#{name_or_adapter_or_class.name.demodulize.remove('Adapter').underscore}) " \
44
- "or an instance (.queue_adapter = #{name_or_adapter_or_class.name}.new) instead."
45
- name_or_adapter_or_class.new
31
+ def interpret_adapter(name_or_adapter_or_class)
32
+ case name_or_adapter_or_class
33
+ when Symbol, String
34
+ ActiveJob::QueueAdapters.lookup(name_or_adapter_or_class).new
46
35
  else
47
- raise ArgumentError
36
+ if queue_adapter?(name_or_adapter_or_class)
37
+ name_or_adapter_or_class
38
+ else
39
+ raise ArgumentError
40
+ end
48
41
  end
49
42
  end
50
- end
51
-
52
- QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
53
43
 
54
- def queue_adapter?(object)
55
- QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
56
- end
44
+ QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
57
45
 
58
- def queue_adapter_class?(object)
59
- object.is_a?(Class) && QUEUE_ADAPTER_METHODS.all? { |meth| object.public_method_defined?(meth) }
60
- end
46
+ def queue_adapter?(object)
47
+ QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
48
+ end
61
49
  end
62
50
  end
63
51
  end
@@ -8,7 +8,7 @@ module ActiveJob
8
8
  # * {Qu}[https://github.com/bkeepers/qu]
9
9
  # * {Que}[https://github.com/chanks/que]
10
10
  # * {queue_classic}[https://github.com/QueueClassic/queue_classic]
11
- # * {Resque 1.x}[https://github.com/resque/resque/tree/1-x-stable]
11
+ # * {Resque}[https://github.com/resque/resque]
12
12
  # * {Sidekiq}[http://sidekiq.org]
13
13
  # * {Sneakers}[https://github.com/jondot/sneakers]
14
14
  # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
@@ -121,7 +121,7 @@ module ActiveJob
121
121
  autoload :SuckerPunchAdapter
122
122
  autoload :TestAdapter
123
123
 
124
- ADAPTER = 'Adapter'.freeze
124
+ ADAPTER = "Adapter".freeze
125
125
  private_constant :ADAPTER
126
126
 
127
127
  class << self
@@ -1,7 +1,7 @@
1
- require 'securerandom'
2
- require 'concurrent/scheduled_task'
3
- require 'concurrent/executor/thread_pool_executor'
4
- require 'concurrent/utility/processor_counter'
1
+ require "securerandom"
2
+ require "concurrent/scheduled_task"
3
+ require "concurrent/executor/thread_pool_executor"
4
+ require "concurrent/utility/processor_counter"
5
5
 
6
6
  module ActiveJob
7
7
  module QueueAdapters