activejob 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +80 -0
- data/MIT-LICENSE +21 -0
- data/README.md +126 -0
- data/lib/active_job.rb +39 -0
- data/lib/active_job/arguments.rb +165 -0
- data/lib/active_job/base.rb +74 -0
- data/lib/active_job/callbacks.rb +155 -0
- data/lib/active_job/configured_job.rb +18 -0
- data/lib/active_job/core.rb +158 -0
- data/lib/active_job/enqueuing.rb +59 -0
- data/lib/active_job/exceptions.rb +134 -0
- data/lib/active_job/execution.rb +49 -0
- data/lib/active_job/gem_version.rb +17 -0
- data/lib/active_job/logging.rb +130 -0
- data/lib/active_job/queue_adapter.rb +60 -0
- data/lib/active_job/queue_adapters.rb +139 -0
- data/lib/active_job/queue_adapters/async_adapter.rb +116 -0
- data/lib/active_job/queue_adapters/backburner_adapter.rb +36 -0
- data/lib/active_job/queue_adapters/delayed_job_adapter.rb +47 -0
- data/lib/active_job/queue_adapters/inline_adapter.rb +23 -0
- data/lib/active_job/queue_adapters/qu_adapter.rb +46 -0
- data/lib/active_job/queue_adapters/que_adapter.rb +39 -0
- data/lib/active_job/queue_adapters/queue_classic_adapter.rb +58 -0
- data/lib/active_job/queue_adapters/resque_adapter.rb +53 -0
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +47 -0
- data/lib/active_job/queue_adapters/sneakers_adapter.rb +48 -0
- data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +49 -0
- data/lib/active_job/queue_adapters/test_adapter.rb +67 -0
- data/lib/active_job/queue_name.rb +49 -0
- data/lib/active_job/queue_priority.rb +43 -0
- data/lib/active_job/railtie.rb +34 -0
- data/lib/active_job/test_case.rb +11 -0
- data/lib/active_job/test_helper.rb +456 -0
- data/lib/active_job/translation.rb +13 -0
- data/lib/active_job/version.rb +10 -0
- data/lib/rails/generators/job/job_generator.rb +40 -0
- data/lib/rails/generators/job/templates/application_job.rb.tt +9 -0
- data/lib/rails/generators/job/templates/job.rb.tt +9 -0
- 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
|