good_job 1.7.0 → 1.9.2
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 +4 -4
- data/CHANGELOG.md +104 -9
- data/README.md +26 -18
- data/exe/good_job +3 -2
- data/lib/active_job/queue_adapters/good_job_adapter.rb +0 -4
- data/lib/good_job.rb +48 -7
- data/lib/good_job/adapter.rb +50 -18
- data/lib/good_job/cli.rb +23 -8
- data/lib/good_job/configuration.rb +46 -40
- data/lib/good_job/execution_result.rb +20 -0
- data/lib/good_job/job.rb +35 -37
- data/lib/good_job/job_performer.rb +4 -3
- data/lib/good_job/lockable.rb +2 -2
- data/lib/good_job/multi_scheduler.rb +12 -7
- data/lib/good_job/notifier.rb +43 -35
- data/lib/good_job/poller.rb +31 -21
- data/lib/good_job/scheduler.rb +119 -71
- data/lib/good_job/version.rb +1 -1
- metadata +4 -17
data/lib/good_job/notifier.rb
CHANGED
@@ -15,7 +15,7 @@ module GoodJob # :nodoc:
|
|
15
15
|
# Default Postgres channel for LISTEN/NOTIFY
|
16
16
|
CHANNEL = 'good_job'.freeze
|
17
17
|
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
18
|
-
|
18
|
+
EXECUTOR_OPTIONS = {
|
19
19
|
name: name,
|
20
20
|
min_threads: 0,
|
21
21
|
max_threads: 1,
|
@@ -30,13 +30,13 @@ module GoodJob # :nodoc:
|
|
30
30
|
# @!attribute [r] instances
|
31
31
|
# @!scope class
|
32
32
|
# List of all instantiated Notifiers in the current process.
|
33
|
-
# @return [
|
33
|
+
# @return [Array<GoodJob::Adapter>]
|
34
34
|
cattr_reader :instances, default: [], instance_reader: false
|
35
35
|
|
36
36
|
# Send a message via Postgres NOTIFY
|
37
37
|
# @param message [#to_json]
|
38
38
|
def self.notify(message)
|
39
|
-
connection =
|
39
|
+
connection = Job.connection
|
40
40
|
connection.exec_query <<~SQL.squish
|
41
41
|
NOTIFY #{CHANNEL}, #{connection.quote(message.to_json)}
|
42
42
|
SQL
|
@@ -53,7 +53,7 @@ module GoodJob # :nodoc:
|
|
53
53
|
|
54
54
|
self.class.instances << self
|
55
55
|
|
56
|
-
|
56
|
+
create_executor
|
57
57
|
listen
|
58
58
|
end
|
59
59
|
|
@@ -63,34 +63,42 @@ module GoodJob # :nodoc:
|
|
63
63
|
@listening.true?
|
64
64
|
end
|
65
65
|
|
66
|
-
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
listen
|
74
|
-
end
|
66
|
+
# Tests whether the notifier is running.
|
67
|
+
# @return [true, false, nil]
|
68
|
+
delegate :running?, to: :executor, allow_nil: true
|
69
|
+
|
70
|
+
# Tests whether the scheduler is shutdown.
|
71
|
+
# @return [true, false, nil]
|
72
|
+
delegate :shutdown?, to: :executor, allow_nil: true
|
75
73
|
|
76
74
|
# Shut down the notifier.
|
77
75
|
# This stops the background LISTENing thread.
|
78
|
-
# If +wait+ is +true+, the notifier will wait for background thread to shutdown.
|
79
|
-
# If +wait+ is +false+, this method will return immediately even though threads may still be running.
|
80
76
|
# Use {#shutdown?} to determine whether threads have stopped.
|
81
|
-
# @param
|
77
|
+
# @param timeout [nil, Numeric] Seconds to wait for active threads.
|
78
|
+
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
79
|
+
# * +-1+, the scheduler will wait until the shutdown is complete.
|
80
|
+
# * +0+, the scheduler will immediately shutdown and stop any threads.
|
81
|
+
# * A positive number will wait that many seconds before stopping any remaining active threads.
|
82
82
|
# @return [void]
|
83
|
-
def shutdown(
|
84
|
-
return
|
83
|
+
def shutdown(timeout: -1)
|
84
|
+
return if executor.nil? || executor.shutdown?
|
85
85
|
|
86
|
-
|
87
|
-
|
86
|
+
executor.shutdown if executor.running?
|
87
|
+
|
88
|
+
if executor.shuttingdown? && timeout # rubocop:disable Style/GuardClause
|
89
|
+
executor_wait = timeout.negative? ? nil : timeout
|
90
|
+
executor.kill unless executor.wait_for_termination(executor_wait)
|
91
|
+
end
|
88
92
|
end
|
89
93
|
|
90
|
-
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
94
|
+
# Restart the notifier.
|
95
|
+
# When shutdown, start; or shutdown and start.
|
96
|
+
# @param timeout [nil, Numeric] Seconds to wait; shares same values as {#shutdown}.
|
97
|
+
# @return [void]
|
98
|
+
def restart(timeout: -1)
|
99
|
+
shutdown(timeout: timeout) if running?
|
100
|
+
create_executor
|
101
|
+
listen
|
94
102
|
end
|
95
103
|
|
96
104
|
# Invoked on completion of ThreadPoolExecutor task
|
@@ -109,36 +117,36 @@ module GoodJob # :nodoc:
|
|
109
117
|
|
110
118
|
private
|
111
119
|
|
112
|
-
|
113
|
-
|
120
|
+
attr_reader :executor
|
121
|
+
|
122
|
+
def create_executor
|
123
|
+
@executor = Concurrent::ThreadPoolExecutor.new(EXECUTOR_OPTIONS)
|
114
124
|
end
|
115
125
|
|
116
126
|
def listen
|
117
|
-
future = Concurrent::Future.new(args: [@recipients,
|
127
|
+
future = Concurrent::Future.new(args: [@recipients, executor, @listening], executor: @executor) do |thr_recipients, thr_executor, thr_listening|
|
118
128
|
with_listen_connection do |conn|
|
119
129
|
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
120
130
|
conn.async_exec("LISTEN #{CHANNEL}").clear
|
121
131
|
end
|
122
132
|
|
123
133
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
124
|
-
|
125
|
-
|
134
|
+
thr_listening.make_true
|
135
|
+
while thr_executor.running?
|
126
136
|
conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
|
127
|
-
listening.make_false
|
128
137
|
next unless channel == CHANNEL
|
129
138
|
|
130
139
|
ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
|
131
140
|
parsed_payload = JSON.parse(payload, symbolize_names: true)
|
132
|
-
|
141
|
+
thr_recipients.each do |recipient|
|
133
142
|
target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
|
134
143
|
target.send(method_name, parsed_payload)
|
135
144
|
end
|
136
145
|
end
|
137
|
-
listening.make_false
|
138
146
|
end
|
139
147
|
end
|
140
148
|
ensure
|
141
|
-
|
149
|
+
thr_listening.make_false
|
142
150
|
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
143
151
|
conn.async_exec("UNLISTEN *").clear
|
144
152
|
end
|
@@ -150,8 +158,8 @@ module GoodJob # :nodoc:
|
|
150
158
|
end
|
151
159
|
|
152
160
|
def with_listen_connection
|
153
|
-
ar_conn =
|
154
|
-
|
161
|
+
ar_conn = Job.connection_pool.checkout.tap do |conn|
|
162
|
+
Job.connection_pool.remove(conn)
|
155
163
|
end
|
156
164
|
pg_conn = ar_conn.raw_connection
|
157
165
|
raise AdapterCannotListenError unless pg_conn.respond_to? :wait_for_notify
|
data/lib/good_job/poller.rb
CHANGED
@@ -16,7 +16,7 @@ module GoodJob # :nodoc:
|
|
16
16
|
# @!attribute [r] instances
|
17
17
|
# @!scope class
|
18
18
|
# List of all instantiated Pollers in the current process.
|
19
|
-
# @return [
|
19
|
+
# @return [Array<GoodJob::Poller>]
|
20
20
|
cattr_reader :instances, default: [], instance_reader: false
|
21
21
|
|
22
22
|
# Creates GoodJob::Poller from a GoodJob::Configuration instance.
|
@@ -40,35 +40,43 @@ module GoodJob # :nodoc:
|
|
40
40
|
|
41
41
|
self.class.instances << self
|
42
42
|
|
43
|
-
|
43
|
+
create_timer
|
44
44
|
end
|
45
45
|
|
46
|
-
#
|
47
|
-
#
|
48
|
-
|
46
|
+
# Tests whether the timer is running.
|
47
|
+
# @return [true, false, nil]
|
48
|
+
delegate :running?, to: :timer, allow_nil: true
|
49
|
+
|
50
|
+
# Tests whether the timer is shutdown.
|
51
|
+
# @return [true, false, nil]
|
52
|
+
delegate :shutdown?, to: :timer, allow_nil: true
|
53
|
+
|
54
|
+
# Shut down the notifier.
|
49
55
|
# Use {#shutdown?} to determine whether threads have stopped.
|
50
|
-
# @param
|
56
|
+
# @param timeout [nil, Numeric] Seconds to wait for active threads.
|
57
|
+
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
58
|
+
# * +-1+, the scheduler will wait until the shutdown is complete.
|
59
|
+
# * +0+, the scheduler will immediately shutdown and stop any threads.
|
60
|
+
# * A positive number will wait that many seconds before stopping any remaining active threads.
|
51
61
|
# @return [void]
|
52
|
-
def shutdown(
|
53
|
-
return
|
62
|
+
def shutdown(timeout: -1)
|
63
|
+
return if timer.nil? || timer.shutdown?
|
54
64
|
|
55
|
-
|
56
|
-
@timer.wait_for_termination if wait
|
57
|
-
end
|
65
|
+
timer.shutdown if timer.running?
|
58
66
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
67
|
+
if timer.shuttingdown? && timeout # rubocop:disable Style/GuardClause
|
68
|
+
timer_wait = timeout.negative? ? nil : timeout
|
69
|
+
timer.kill unless timer.wait_for_termination(timer_wait)
|
70
|
+
end
|
63
71
|
end
|
64
72
|
|
65
73
|
# Restart the poller.
|
66
74
|
# When shutdown, start; or shutdown and start.
|
67
|
-
# @param
|
75
|
+
# @param timeout [nil, Numeric] Seconds to wait; shares same values as {#shutdown}.
|
68
76
|
# @return [void]
|
69
|
-
def restart(
|
70
|
-
shutdown(
|
71
|
-
|
77
|
+
def restart(timeout: -1)
|
78
|
+
shutdown(timeout: timeout) if running?
|
79
|
+
create_timer
|
72
80
|
end
|
73
81
|
|
74
82
|
# Invoked on completion of TimerTask task.
|
@@ -76,12 +84,14 @@ module GoodJob # :nodoc:
|
|
76
84
|
# @return [void]
|
77
85
|
def timer_observer(time, executed_task, thread_error)
|
78
86
|
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
79
|
-
instrument("finished_timer_task", { result: executed_task, error: thread_error, time: time })
|
87
|
+
ActiveSupport::Notifications.instrument("finished_timer_task", { result: executed_task, error: thread_error, time: time })
|
80
88
|
end
|
81
89
|
|
82
90
|
private
|
83
91
|
|
84
|
-
|
92
|
+
attr_reader :timer
|
93
|
+
|
94
|
+
def create_timer
|
85
95
|
return if @timer_options[:execution_interval] <= 0
|
86
96
|
|
87
97
|
@timer = Concurrent::TimerTask.new(@timer_options) do
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -16,28 +16,28 @@ module GoodJob # :nodoc:
|
|
16
16
|
#
|
17
17
|
class Scheduler
|
18
18
|
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
19
|
-
# The thread pool is where work is performed.
|
20
|
-
|
19
|
+
# The thread pool executor is where work is performed.
|
20
|
+
DEFAULT_EXECUTOR_OPTIONS = {
|
21
21
|
name: name,
|
22
22
|
min_threads: 0,
|
23
23
|
max_threads: Configuration::DEFAULT_MAX_THREADS,
|
24
24
|
auto_terminate: true,
|
25
25
|
idletime: 60,
|
26
|
-
max_queue:
|
26
|
+
max_queue: Configuration::DEFAULT_MAX_THREADS,
|
27
27
|
fallback_policy: :discard,
|
28
28
|
}.freeze
|
29
29
|
|
30
30
|
# @!attribute [r] instances
|
31
31
|
# @!scope class
|
32
32
|
# List of all instantiated Schedulers in the current process.
|
33
|
-
# @return [
|
33
|
+
# @return [Array<GoodJob::Scheduler>]
|
34
34
|
cattr_reader :instances, default: [], instance_reader: false
|
35
35
|
|
36
36
|
# Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
|
37
37
|
# @param configuration [GoodJob::Configuration]
|
38
38
|
# @param warm_cache_on_initialize [Boolean]
|
39
39
|
# @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
|
40
|
-
def self.from_configuration(configuration, warm_cache_on_initialize:
|
40
|
+
def self.from_configuration(configuration, warm_cache_on_initialize: false)
|
41
41
|
schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
|
42
42
|
queue_string, max_threads = queue_string_and_max_threads.split(':')
|
43
43
|
max_threads = (max_threads || configuration.max_threads).to_i
|
@@ -61,8 +61,8 @@ module GoodJob # :nodoc:
|
|
61
61
|
# @param performer [GoodJob::JobPerformer]
|
62
62
|
# @param max_threads [Numeric, nil] number of seconds between polls for jobs
|
63
63
|
# @param max_cache [Numeric, nil] maximum number of scheduled jobs to cache in memory
|
64
|
-
# @param warm_cache_on_initialize [Boolean] whether to warm the cache immediately
|
65
|
-
def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize:
|
64
|
+
# @param warm_cache_on_initialize [Boolean] whether to warm the cache immediately, or manually by calling +warm_cache+
|
65
|
+
def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize: false)
|
66
66
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
67
67
|
|
68
68
|
self.class.instances << self
|
@@ -70,63 +70,75 @@ module GoodJob # :nodoc:
|
|
70
70
|
@performer = performer
|
71
71
|
|
72
72
|
@max_cache = max_cache || 0
|
73
|
-
@
|
74
|
-
|
75
|
-
|
73
|
+
@executor_options = DEFAULT_EXECUTOR_OPTIONS.dup
|
74
|
+
if max_threads.present?
|
75
|
+
@executor_options[:max_threads] = max_threads
|
76
|
+
@executor_options[:max_queue] = max_threads
|
77
|
+
end
|
78
|
+
@executor_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@executor_options[:max_threads]})"
|
76
79
|
|
77
|
-
|
80
|
+
create_executor
|
78
81
|
warm_cache if warm_cache_on_initialize
|
79
82
|
end
|
80
83
|
|
84
|
+
# Tests whether the scheduler is running.
|
85
|
+
# @return [true, false, nil]
|
86
|
+
delegate :running?, to: :executor, allow_nil: true
|
87
|
+
|
88
|
+
# Tests whether the scheduler is shutdown.
|
89
|
+
# @return [true, false, nil]
|
90
|
+
delegate :shutdown?, to: :executor, allow_nil: true
|
91
|
+
|
81
92
|
# Shut down the scheduler.
|
82
|
-
# This stops all threads in the pool.
|
83
|
-
# If +wait+ is +true+, the scheduler will wait for any active tasks to finish.
|
84
|
-
# If +wait+ is +false+, this method will return immediately even though threads may still be running.
|
93
|
+
# This stops all threads in the thread pool.
|
85
94
|
# Use {#shutdown?} to determine whether threads have stopped.
|
86
|
-
# @param
|
95
|
+
# @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish
|
96
|
+
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
97
|
+
# * +-1+, the scheduler will wait until the shutdown is complete.
|
98
|
+
# * +0+, the scheduler will immediately shutdown and stop any active tasks.
|
99
|
+
# * A positive number will wait that many seconds before stopping any remaining active tasks.
|
87
100
|
# @return [void]
|
88
|
-
def shutdown(
|
89
|
-
return
|
90
|
-
|
91
|
-
instrument("scheduler_shutdown_start", {
|
92
|
-
instrument("scheduler_shutdown", {
|
93
|
-
|
101
|
+
def shutdown(timeout: -1)
|
102
|
+
return if executor.nil? || executor.shutdown?
|
103
|
+
|
104
|
+
instrument("scheduler_shutdown_start", { timeout: timeout })
|
105
|
+
instrument("scheduler_shutdown", { timeout: timeout }) do
|
106
|
+
if executor.running?
|
107
|
+
@timer_set.shutdown
|
108
|
+
executor.shutdown
|
109
|
+
end
|
94
110
|
|
95
|
-
|
96
|
-
|
97
|
-
|
111
|
+
if executor.shuttingdown? && timeout
|
112
|
+
executor_wait = timeout.negative? ? nil : timeout
|
113
|
+
executor.kill unless executor.wait_for_termination(executor_wait)
|
114
|
+
end
|
98
115
|
end
|
99
116
|
end
|
100
117
|
|
101
|
-
# Tests whether the scheduler is shutdown.
|
102
|
-
# @return [true, false, nil]
|
103
|
-
def shutdown?
|
104
|
-
!@pool&.running?
|
105
|
-
end
|
106
|
-
|
107
118
|
# Restart the Scheduler.
|
108
119
|
# When shutdown, start; or shutdown and start.
|
109
|
-
# @param
|
120
|
+
# @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish; shares same values as {#shutdown}.
|
110
121
|
# @return [void]
|
111
|
-
def restart(
|
122
|
+
def restart(timeout: -1)
|
112
123
|
instrument("scheduler_restart_pools") do
|
113
|
-
shutdown(
|
114
|
-
|
124
|
+
shutdown(timeout: timeout) if running?
|
125
|
+
create_executor
|
115
126
|
warm_cache
|
116
127
|
end
|
117
128
|
end
|
118
129
|
|
119
130
|
# Wakes a thread to allow the performer to execute a task.
|
120
|
-
# @param state [nil, Object] Contextual information for the performer. See {
|
131
|
+
# @param state [nil, Object] Contextual information for the performer. See {JobPerformer#next?}.
|
121
132
|
# @return [nil, Boolean] Whether work was started.
|
122
|
-
#
|
123
|
-
#
|
124
|
-
#
|
133
|
+
#
|
134
|
+
# * +nil+ if the scheduler is unable to take new work, for example if the thread pool is shut down or at capacity.
|
135
|
+
# * +true+ if the performer started executing work.
|
136
|
+
# * +false+ if the performer decides not to attempt to execute a task based on the +state+ that is passed to it.
|
125
137
|
def create_thread(state = nil)
|
126
|
-
return nil unless
|
138
|
+
return nil unless executor.running?
|
127
139
|
|
128
140
|
if state
|
129
|
-
return false unless
|
141
|
+
return false unless performer.next?(state)
|
130
142
|
|
131
143
|
if state[:scheduled_at]
|
132
144
|
scheduled_at = if state[:scheduled_at].is_a? String
|
@@ -141,18 +153,12 @@ module GoodJob # :nodoc:
|
|
141
153
|
delay ||= 0
|
142
154
|
run_now = delay <= 0.01
|
143
155
|
if run_now
|
144
|
-
return nil unless
|
156
|
+
return nil unless executor.ready_worker_count.positive?
|
145
157
|
elsif @max_cache.positive?
|
146
158
|
return nil unless remaining_cache_count.positive?
|
147
159
|
end
|
148
160
|
|
149
|
-
|
150
|
-
output = nil
|
151
|
-
Rails.application.executor.wrap { output = performer.next }
|
152
|
-
output
|
153
|
-
end
|
154
|
-
future.add_observer(self, :task_observer)
|
155
|
-
future.execute
|
161
|
+
create_task(delay)
|
156
162
|
|
157
163
|
run_now ? true : nil
|
158
164
|
end
|
@@ -163,43 +169,69 @@ module GoodJob # :nodoc:
|
|
163
169
|
def task_observer(time, output, thread_error)
|
164
170
|
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
165
171
|
instrument("finished_job_task", { result: output, error: thread_error, time: time })
|
166
|
-
|
172
|
+
create_task if output
|
167
173
|
end
|
168
174
|
|
175
|
+
# Information about the Scheduler
|
176
|
+
# @return [Hash]
|
177
|
+
def stats
|
178
|
+
{
|
179
|
+
name: performer.name,
|
180
|
+
max_threads: @executor_options[:max_threads],
|
181
|
+
active_threads: @executor_options[:max_threads] - executor.ready_worker_count,
|
182
|
+
available_threads: executor.ready_worker_count,
|
183
|
+
max_cache: @max_cache,
|
184
|
+
active_cache: cache_count,
|
185
|
+
available_cache: remaining_cache_count,
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
# Preload existing runnable and future-scheduled jobs
|
190
|
+
# @return [void]
|
169
191
|
def warm_cache
|
170
192
|
return if @max_cache.zero?
|
171
193
|
|
172
|
-
@performer
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
194
|
+
future = Concurrent::Future.new(args: [self, @performer], executor: executor) do |thr_scheduler, thr_performer|
|
195
|
+
Rails.application.executor.wrap do
|
196
|
+
thr_performer.next_at(
|
197
|
+
limit: @max_cache,
|
198
|
+
now_limit: @executor_options[:max_threads]
|
199
|
+
).each do |scheduled_at|
|
200
|
+
thr_scheduler.create_thread({ scheduled_at: scheduled_at })
|
201
|
+
end
|
202
|
+
end
|
177
203
|
end
|
178
|
-
end
|
179
204
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
cache_count: cache_count,
|
188
|
-
cache_remaining: remaining_cache_count,
|
189
|
-
}
|
205
|
+
observer = lambda do |_time, _output, thread_error|
|
206
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
207
|
+
create_task # If cache-warming exhausts the threads, ensure there isn't an executable task remaining
|
208
|
+
end
|
209
|
+
future.add_observer(observer, :call)
|
210
|
+
|
211
|
+
future.execute
|
190
212
|
end
|
191
213
|
|
192
214
|
private
|
193
215
|
|
194
|
-
attr_reader :timer_set
|
216
|
+
attr_reader :performer, :executor, :timer_set
|
195
217
|
|
196
|
-
def
|
197
|
-
instrument("scheduler_create_pool", { performer_name:
|
198
|
-
@timer_set =
|
199
|
-
@
|
218
|
+
def create_executor
|
219
|
+
instrument("scheduler_create_pool", { performer_name: performer.name, max_threads: @executor_options[:max_threads] }) do
|
220
|
+
@timer_set = TimerSet.new
|
221
|
+
@executor = ThreadPoolExecutor.new(@executor_options)
|
200
222
|
end
|
201
223
|
end
|
202
224
|
|
225
|
+
def create_task(delay = 0)
|
226
|
+
future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
|
227
|
+
Rails.application.executor.wrap do
|
228
|
+
thr_performer.next
|
229
|
+
end
|
230
|
+
end
|
231
|
+
future.add_observer(self, :task_observer)
|
232
|
+
future.execute
|
233
|
+
end
|
234
|
+
|
203
235
|
def instrument(name, payload = {}, &block)
|
204
236
|
payload = payload.reverse_merge({
|
205
237
|
scheduler: self,
|
@@ -211,7 +243,7 @@ module GoodJob # :nodoc:
|
|
211
243
|
end
|
212
244
|
|
213
245
|
def cache_count
|
214
|
-
timer_set.
|
246
|
+
timer_set.length
|
215
247
|
end
|
216
248
|
|
217
249
|
def remaining_cache_count
|
@@ -236,5 +268,21 @@ module GoodJob # :nodoc:
|
|
236
268
|
end
|
237
269
|
end
|
238
270
|
end
|
271
|
+
|
272
|
+
# Custom sub-class of +Concurrent::TimerSet+ for additional behavior.
|
273
|
+
# @private
|
274
|
+
class TimerSet < Concurrent::TimerSet
|
275
|
+
# Number of scheduled jobs in the queue
|
276
|
+
# @return [Integer]
|
277
|
+
def length
|
278
|
+
@queue.length
|
279
|
+
end
|
280
|
+
|
281
|
+
# Clear the queue
|
282
|
+
# @return [void]
|
283
|
+
def reset
|
284
|
+
synchronize { @queue.clear }
|
285
|
+
end
|
286
|
+
end
|
239
287
|
end
|
240
288
|
end
|