activejob 5.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +80 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.md +126 -0
  5. data/lib/active_job.rb +39 -0
  6. data/lib/active_job/arguments.rb +165 -0
  7. data/lib/active_job/base.rb +74 -0
  8. data/lib/active_job/callbacks.rb +155 -0
  9. data/lib/active_job/configured_job.rb +18 -0
  10. data/lib/active_job/core.rb +158 -0
  11. data/lib/active_job/enqueuing.rb +59 -0
  12. data/lib/active_job/exceptions.rb +134 -0
  13. data/lib/active_job/execution.rb +49 -0
  14. data/lib/active_job/gem_version.rb +17 -0
  15. data/lib/active_job/logging.rb +130 -0
  16. data/lib/active_job/queue_adapter.rb +60 -0
  17. data/lib/active_job/queue_adapters.rb +139 -0
  18. data/lib/active_job/queue_adapters/async_adapter.rb +116 -0
  19. data/lib/active_job/queue_adapters/backburner_adapter.rb +36 -0
  20. data/lib/active_job/queue_adapters/delayed_job_adapter.rb +47 -0
  21. data/lib/active_job/queue_adapters/inline_adapter.rb +23 -0
  22. data/lib/active_job/queue_adapters/qu_adapter.rb +46 -0
  23. data/lib/active_job/queue_adapters/que_adapter.rb +39 -0
  24. data/lib/active_job/queue_adapters/queue_classic_adapter.rb +58 -0
  25. data/lib/active_job/queue_adapters/resque_adapter.rb +53 -0
  26. data/lib/active_job/queue_adapters/sidekiq_adapter.rb +47 -0
  27. data/lib/active_job/queue_adapters/sneakers_adapter.rb +48 -0
  28. data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +49 -0
  29. data/lib/active_job/queue_adapters/test_adapter.rb +67 -0
  30. data/lib/active_job/queue_name.rb +49 -0
  31. data/lib/active_job/queue_priority.rb +43 -0
  32. data/lib/active_job/railtie.rb +34 -0
  33. data/lib/active_job/test_case.rb +11 -0
  34. data/lib/active_job/test_helper.rb +456 -0
  35. data/lib/active_job/translation.rb +13 -0
  36. data/lib/active_job/version.rb +10 -0
  37. data/lib/rails/generators/job/job_generator.rb +40 -0
  38. data/lib/rails/generators/job/templates/application_job.rb.tt +9 -0
  39. data/lib/rails/generators/job/templates/job.rb.tt +9 -0
  40. metadata +110 -0
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/rescuable"
4
+ require "active_job/arguments"
5
+
6
+ module ActiveJob
7
+ module Execution
8
+ extend ActiveSupport::Concern
9
+ include ActiveSupport::Rescuable
10
+
11
+ # Includes methods for executing and performing jobs instantly.
12
+ module ClassMethods
13
+ # Performs the job immediately.
14
+ #
15
+ # MyJob.perform_now("mike")
16
+ #
17
+ def perform_now(*args)
18
+ job_or_instantiate(*args).perform_now
19
+ end
20
+
21
+ def execute(job_data) #:nodoc:
22
+ ActiveJob::Callbacks.run_callbacks(:execute) do
23
+ job = deserialize(job_data)
24
+ job.perform_now
25
+ end
26
+ end
27
+ end
28
+
29
+ # Performs the job immediately. The job is not sent to the queueing adapter
30
+ # but directly executed by blocking the execution of others until it's finished.
31
+ #
32
+ # MyJob.new(*args).perform_now
33
+ def perform_now
34
+ # Guard against jobs that were persisted before we started counting executions by zeroing out nil counters
35
+ self.executions = (executions || 0) + 1
36
+
37
+ deserialize_arguments_if_needed
38
+ run_callbacks :perform do
39
+ perform(*arguments)
40
+ end
41
+ rescue => exception
42
+ rescue_with_handler(exception) || raise
43
+ end
44
+
45
+ def perform(*)
46
+ fail NotImplementedError
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ # Returns the version of the currently loaded Active Job as a <tt>Gem::Version</tt>
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 5
11
+ MINOR = 2
12
+ TINY = 3
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
+ end
17
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/transform_values"
4
+ require "active_support/core_ext/string/filters"
5
+ require "active_support/tagged_logging"
6
+ require "active_support/logger"
7
+
8
+ module ActiveJob
9
+ module Logging #:nodoc:
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ cattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
14
+
15
+ around_enqueue do |_, block, _|
16
+ tag_logger do
17
+ block.call
18
+ end
19
+ end
20
+
21
+ around_perform do |job, block, _|
22
+ tag_logger(job.class.name, job.job_id) do
23
+ payload = { adapter: job.class.queue_adapter, job: job }
24
+ ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
25
+ ActiveSupport::Notifications.instrument("perform.active_job", payload) do
26
+ block.call
27
+ end
28
+ end
29
+ end
30
+
31
+ after_enqueue do |job|
32
+ if job.scheduled_at
33
+ ActiveSupport::Notifications.instrument "enqueue_at.active_job",
34
+ adapter: job.class.queue_adapter, job: job
35
+ else
36
+ ActiveSupport::Notifications.instrument "enqueue.active_job",
37
+ adapter: job.class.queue_adapter, job: job
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+ def tag_logger(*tags)
44
+ if logger.respond_to?(:tagged)
45
+ tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
46
+ logger.tagged(*tags) { yield }
47
+ else
48
+ yield
49
+ end
50
+ end
51
+
52
+ def logger_tagged_by_active_job?
53
+ logger.formatter.current_tags.include?("ActiveJob")
54
+ end
55
+
56
+ class LogSubscriber < ActiveSupport::LogSubscriber #:nodoc:
57
+ def enqueue(event)
58
+ info do
59
+ job = event.payload[:job]
60
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)}" + args_info(job)
61
+ end
62
+ end
63
+
64
+ def enqueue_at(event)
65
+ info do
66
+ job = event.payload[:job]
67
+ "Enqueued #{job.class.name} (Job ID: #{job.job_id}) to #{queue_name(event)} at #{scheduled_at(event)}" + args_info(job)
68
+ end
69
+ end
70
+
71
+ def perform_start(event)
72
+ info do
73
+ job = event.payload[:job]
74
+ "Performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)}" + args_info(job)
75
+ end
76
+ end
77
+
78
+ def perform(event)
79
+ job = event.payload[:job]
80
+ ex = event.payload[:exception_object]
81
+ if ex
82
+ error do
83
+ "Error performing #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms: #{ex.class} (#{ex.message}):\n" + Array(ex.backtrace).join("\n")
84
+ end
85
+ else
86
+ info do
87
+ "Performed #{job.class.name} (Job ID: #{job.job_id}) from #{queue_name(event)} in #{event.duration.round(2)}ms"
88
+ end
89
+ end
90
+ end
91
+
92
+ private
93
+ def queue_name(event)
94
+ event.payload[:adapter].class.name.demodulize.remove("Adapter") + "(#{event.payload[:job].queue_name})"
95
+ end
96
+
97
+ def args_info(job)
98
+ if job.arguments.any?
99
+ " with arguments: " +
100
+ job.arguments.map { |arg| format(arg).inspect }.join(", ")
101
+ else
102
+ ""
103
+ end
104
+ end
105
+
106
+ def format(arg)
107
+ case arg
108
+ when Hash
109
+ arg.transform_values { |value| format(value) }
110
+ when Array
111
+ arg.map { |value| format(value) }
112
+ when GlobalID::Identification
113
+ arg.to_global_id rescue arg
114
+ else
115
+ arg
116
+ end
117
+ end
118
+
119
+ def scheduled_at(event)
120
+ Time.at(event.payload[:job].scheduled_at).utc
121
+ end
122
+
123
+ def logger
124
+ ActiveJob::Base.logger
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ ActiveJob::Logging::LogSubscriber.attach_to :active_job
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/inflections"
4
+
5
+ module ActiveJob
6
+ # The <tt>ActiveJob::QueueAdapter</tt> module is used to load the
7
+ # correct adapter. The default queue adapter is the +:async+ queue.
8
+ module QueueAdapter #:nodoc:
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ class_attribute :_queue_adapter_name, instance_accessor: false, instance_predicate: false
13
+ class_attribute :_queue_adapter, instance_accessor: false, instance_predicate: false
14
+ self.queue_adapter = :async
15
+ end
16
+
17
+ # Includes the setter method for changing the active queue adapter.
18
+ module ClassMethods
19
+ # Returns the backend queue provider. The default queue adapter
20
+ # is the +:async+ queue. See QueueAdapters for more information.
21
+ def queue_adapter
22
+ _queue_adapter
23
+ end
24
+
25
+ def queue_adapter_name
26
+ _queue_adapter_name
27
+ end
28
+
29
+ # Specify the backend queue provider. The default queue adapter
30
+ # is the +:async+ queue. See QueueAdapters for more
31
+ # information.
32
+ def queue_adapter=(name_or_adapter)
33
+ case name_or_adapter
34
+ when Symbol, String
35
+ queue_adapter = ActiveJob::QueueAdapters.lookup(name_or_adapter).new
36
+ assign_adapter(name_or_adapter.to_s, queue_adapter)
37
+ else
38
+ if queue_adapter?(name_or_adapter)
39
+ adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}"
40
+ assign_adapter(adapter_name, name_or_adapter)
41
+ else
42
+ raise ArgumentError
43
+ end
44
+ end
45
+ end
46
+
47
+ private
48
+ def assign_adapter(adapter_name, queue_adapter)
49
+ self._queue_adapter_name = adapter_name
50
+ self._queue_adapter = queue_adapter
51
+ end
52
+
53
+ QUEUE_ADAPTER_METHODS = [:enqueue, :enqueue_at].freeze
54
+
55
+ def queue_adapter?(object)
56
+ QUEUE_ADAPTER_METHODS.all? { |meth| object.respond_to?(meth) }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveJob
4
+ # == Active Job adapters
5
+ #
6
+ # Active Job has adapters for the following queueing backends:
7
+ #
8
+ # * {Backburner}[https://github.com/nesquena/backburner]
9
+ # * {Delayed Job}[https://github.com/collectiveidea/delayed_job]
10
+ # * {Qu}[https://github.com/bkeepers/qu]
11
+ # * {Que}[https://github.com/chanks/que]
12
+ # * {queue_classic}[https://github.com/QueueClassic/queue_classic]
13
+ # * {Resque}[https://github.com/resque/resque]
14
+ # * {Sidekiq}[http://sidekiq.org]
15
+ # * {Sneakers}[https://github.com/jondot/sneakers]
16
+ # * {Sucker Punch}[https://github.com/brandonhilkert/sucker_punch]
17
+ # * {Active Job Async Job}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/AsyncAdapter.html]
18
+ # * {Active Job Inline}[http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters/InlineAdapter.html]
19
+ #
20
+ # === Backends Features
21
+ #
22
+ # | | Async | Queues | Delayed | Priorities | Timeout | Retries |
23
+ # |-------------------|-------|--------|------------|------------|---------|---------|
24
+ # | Backburner | Yes | Yes | Yes | Yes | Job | Global |
25
+ # | Delayed Job | Yes | Yes | Yes | Job | Global | Global |
26
+ # | Qu | Yes | Yes | No | No | No | Global |
27
+ # | Que | Yes | Yes | Yes | Job | No | Job |
28
+ # | queue_classic | Yes | Yes | Yes* | No | No | No |
29
+ # | Resque | Yes | Yes | Yes (Gem) | Queue | Global | Yes |
30
+ # | Sidekiq | Yes | Yes | Yes | Queue | No | Job |
31
+ # | Sneakers | Yes | Yes | No | Queue | Queue | No |
32
+ # | Sucker Punch | Yes | Yes | Yes | No | No | No |
33
+ # | Active Job Async | Yes | Yes | Yes | No | No | No |
34
+ # | Active Job Inline | No | Yes | N/A | N/A | N/A | N/A |
35
+ #
36
+ # ==== Async
37
+ #
38
+ # Yes: The Queue Adapter has the ability to run the job in a non-blocking manner.
39
+ # It either runs on a separate or forked process, or on a different thread.
40
+ #
41
+ # No: The job is run in the same process.
42
+ #
43
+ # ==== Queues
44
+ #
45
+ # Yes: Jobs may set which queue they are run in with queue_as or by using the set
46
+ # method.
47
+ #
48
+ # ==== Delayed
49
+ #
50
+ # Yes: The adapter will run the job in the future through perform_later.
51
+ #
52
+ # (Gem): An additional gem is required to use perform_later with this adapter.
53
+ #
54
+ # No: The adapter will run jobs at the next opportunity and cannot use perform_later.
55
+ #
56
+ # N/A: The adapter does not support queueing.
57
+ #
58
+ # NOTE:
59
+ # queue_classic supports job scheduling since version 3.1.
60
+ # For older versions you can use the queue_classic-later gem.
61
+ #
62
+ # ==== Priorities
63
+ #
64
+ # The order in which jobs are processed can be configured differently depending
65
+ # on the adapter.
66
+ #
67
+ # Job: Any class inheriting from the adapter may set the priority on the job
68
+ # object relative to other jobs.
69
+ #
70
+ # Queue: The adapter can set the priority for job queues, when setting a queue
71
+ # with Active Job this will be respected.
72
+ #
73
+ # Yes: Allows the priority to be set on the job object, at the queue level or
74
+ # as default configuration option.
75
+ #
76
+ # No: Does not allow the priority of jobs to be configured.
77
+ #
78
+ # N/A: The adapter does not support queueing, and therefore sorting them.
79
+ #
80
+ # ==== Timeout
81
+ #
82
+ # When a job will stop after the allotted time.
83
+ #
84
+ # Job: The timeout can be set for each instance of the job class.
85
+ #
86
+ # Queue: The timeout is set for all jobs on the queue.
87
+ #
88
+ # Global: The adapter is configured that all jobs have a maximum run time.
89
+ #
90
+ # N/A: This adapter does not run in a separate process, and therefore timeout
91
+ # is unsupported.
92
+ #
93
+ # ==== Retries
94
+ #
95
+ # Job: The number of retries can be set per instance of the job class.
96
+ #
97
+ # Yes: The Number of retries can be configured globally, for each instance or
98
+ # on the queue. This adapter may also present failed instances of the job class
99
+ # that can be restarted.
100
+ #
101
+ # Global: The adapter has a global number of retries.
102
+ #
103
+ # N/A: The adapter does not run in a separate process, and therefore doesn't
104
+ # support retries.
105
+ #
106
+ # === Async and Inline Queue Adapters
107
+ #
108
+ # Active Job has two built-in queue adapters intended for development and
109
+ # testing: +:async+ and +:inline+.
110
+ module QueueAdapters
111
+ extend ActiveSupport::Autoload
112
+
113
+ autoload :AsyncAdapter
114
+ autoload :InlineAdapter
115
+ autoload :BackburnerAdapter
116
+ autoload :DelayedJobAdapter
117
+ autoload :QuAdapter
118
+ autoload :QueAdapter
119
+ autoload :QueueClassicAdapter
120
+ autoload :ResqueAdapter
121
+ autoload :SidekiqAdapter
122
+ autoload :SneakersAdapter
123
+ autoload :SuckerPunchAdapter
124
+ autoload :TestAdapter
125
+
126
+ ADAPTER = "Adapter".freeze
127
+ private_constant :ADAPTER
128
+
129
+ class << self
130
+ # Returns adapter for specified name.
131
+ #
132
+ # ActiveJob::QueueAdapters.lookup(:sidekiq)
133
+ # # => ActiveJob::QueueAdapters::SidekiqAdapter
134
+ def lookup(name)
135
+ const_get(name.to_s.camelize << ADAPTER)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "concurrent/scheduled_task"
5
+ require "concurrent/executor/thread_pool_executor"
6
+ require "concurrent/utility/processor_counter"
7
+
8
+ module ActiveJob
9
+ module QueueAdapters
10
+ # == Active Job Async adapter
11
+ #
12
+ # The Async adapter runs jobs with an in-process thread pool.
13
+ #
14
+ # This is the default queue adapter. It's well-suited for dev/test since
15
+ # it doesn't need an external infrastructure, but it's a poor fit for
16
+ # production since it drops pending jobs on restart.
17
+ #
18
+ # To use this adapter, set queue adapter to +:async+:
19
+ #
20
+ # config.active_job.queue_adapter = :async
21
+ #
22
+ # To configure the adapter's thread pool, instantiate the adapter and
23
+ # pass your own config:
24
+ #
25
+ # config.active_job.queue_adapter = ActiveJob::QueueAdapters::AsyncAdapter.new \
26
+ # min_threads: 1,
27
+ # max_threads: 2 * Concurrent.processor_count,
28
+ # idletime: 600.seconds
29
+ #
30
+ # The adapter uses a {Concurrent Ruby}[https://github.com/ruby-concurrency/concurrent-ruby] thread pool to schedule and execute
31
+ # jobs. Since jobs share a single thread pool, long-running jobs will block
32
+ # short-lived jobs. Fine for dev/test; bad for production.
33
+ class AsyncAdapter
34
+ # See {Concurrent::ThreadPoolExecutor}[https://ruby-concurrency.github.io/concurrent-ruby/Concurrent/ThreadPoolExecutor.html] for executor options.
35
+ def initialize(**executor_options)
36
+ @scheduler = Scheduler.new(**executor_options)
37
+ end
38
+
39
+ def enqueue(job) #:nodoc:
40
+ @scheduler.enqueue JobWrapper.new(job), queue_name: job.queue_name
41
+ end
42
+
43
+ def enqueue_at(job, timestamp) #:nodoc:
44
+ @scheduler.enqueue_at JobWrapper.new(job), timestamp, queue_name: job.queue_name
45
+ end
46
+
47
+ # Gracefully stop processing jobs. Finishes in-progress work and handles
48
+ # any new jobs following the executor's fallback policy (`caller_runs`).
49
+ # Waits for termination by default. Pass `wait: false` to continue.
50
+ def shutdown(wait: true) #:nodoc:
51
+ @scheduler.shutdown wait: wait
52
+ end
53
+
54
+ # Used for our test suite.
55
+ def immediate=(immediate) #:nodoc:
56
+ @scheduler.immediate = immediate
57
+ end
58
+
59
+ # Note that we don't actually need to serialize the jobs since we're
60
+ # performing them in-process, but we do so anyway for parity with other
61
+ # adapters and deployment environments. Otherwise, serialization bugs
62
+ # may creep in undetected.
63
+ class JobWrapper #:nodoc:
64
+ def initialize(job)
65
+ job.provider_job_id = SecureRandom.uuid
66
+ @job_data = job.serialize
67
+ end
68
+
69
+ def perform
70
+ Base.execute @job_data
71
+ end
72
+ end
73
+
74
+ class Scheduler #:nodoc:
75
+ DEFAULT_EXECUTOR_OPTIONS = {
76
+ min_threads: 0,
77
+ max_threads: Concurrent.processor_count,
78
+ auto_terminate: true,
79
+ idletime: 60, # 1 minute
80
+ max_queue: 0, # unlimited
81
+ fallback_policy: :caller_runs # shouldn't matter -- 0 max queue
82
+ }.freeze
83
+
84
+ attr_accessor :immediate
85
+
86
+ def initialize(**options)
87
+ self.immediate = false
88
+ @immediate_executor = Concurrent::ImmediateExecutor.new
89
+ @async_executor = Concurrent::ThreadPoolExecutor.new(DEFAULT_EXECUTOR_OPTIONS.merge(options))
90
+ end
91
+
92
+ def enqueue(job, queue_name:)
93
+ executor.post(job, &:perform)
94
+ end
95
+
96
+ def enqueue_at(job, timestamp, queue_name:)
97
+ delay = timestamp - Time.current.to_f
98
+ if delay > 0
99
+ Concurrent::ScheduledTask.execute(delay, args: [job], executor: executor, &:perform)
100
+ else
101
+ enqueue(job, queue_name: queue_name)
102
+ end
103
+ end
104
+
105
+ def shutdown(wait: true)
106
+ @async_executor.shutdown
107
+ @async_executor.wait_for_termination if wait
108
+ end
109
+
110
+ def executor
111
+ immediate ? @immediate_executor : @async_executor
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end