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.
- 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
|