good_job 1.7.1 → 1.9.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 +4 -4
- data/CHANGELOG.md +109 -11
- 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 +51 -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 +108 -73
- 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,8 +16,8 @@ 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,
|
@@ -30,14 +30,14 @@ module GoodJob # :nodoc:
|
|
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,66 +70,75 @@ module GoodJob # :nodoc:
|
|
70
70
|
@performer = performer
|
71
71
|
|
72
72
|
@max_cache = max_cache || 0
|
73
|
-
@
|
73
|
+
@executor_options = DEFAULT_EXECUTOR_OPTIONS.dup
|
74
74
|
if max_threads.present?
|
75
|
-
@
|
76
|
-
@
|
75
|
+
@executor_options[:max_threads] = max_threads
|
76
|
+
@executor_options[:max_queue] = max_threads
|
77
77
|
end
|
78
|
-
@
|
78
|
+
@executor_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@executor_options[:max_threads]})"
|
79
79
|
|
80
|
-
|
80
|
+
create_executor
|
81
81
|
warm_cache if warm_cache_on_initialize
|
82
82
|
end
|
83
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
|
+
|
84
92
|
# Shut down the scheduler.
|
85
|
-
# This stops all threads in the pool.
|
86
|
-
# If +wait+ is +true+, the scheduler will wait for any active tasks to finish.
|
87
|
-
# 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.
|
88
94
|
# Use {#shutdown?} to determine whether threads have stopped.
|
89
|
-
# @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.
|
90
100
|
# @return [void]
|
91
|
-
def shutdown(
|
92
|
-
return
|
93
|
-
|
94
|
-
instrument("scheduler_shutdown_start", {
|
95
|
-
instrument("scheduler_shutdown", {
|
96
|
-
|
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
|
97
110
|
|
98
|
-
|
99
|
-
|
100
|
-
|
111
|
+
if executor.shuttingdown? && timeout
|
112
|
+
executor_wait = timeout.negative? ? nil : timeout
|
113
|
+
executor.kill unless executor.wait_for_termination(executor_wait)
|
114
|
+
end
|
101
115
|
end
|
102
116
|
end
|
103
117
|
|
104
|
-
# Tests whether the scheduler is shutdown.
|
105
|
-
# @return [true, false, nil]
|
106
|
-
def shutdown?
|
107
|
-
!@pool&.running?
|
108
|
-
end
|
109
|
-
|
110
118
|
# Restart the Scheduler.
|
111
119
|
# When shutdown, start; or shutdown and start.
|
112
|
-
# @param
|
120
|
+
# @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish; shares same values as {#shutdown}.
|
113
121
|
# @return [void]
|
114
|
-
def restart(
|
122
|
+
def restart(timeout: -1)
|
115
123
|
instrument("scheduler_restart_pools") do
|
116
|
-
shutdown(
|
117
|
-
|
124
|
+
shutdown(timeout: timeout) if running?
|
125
|
+
create_executor
|
118
126
|
warm_cache
|
119
127
|
end
|
120
128
|
end
|
121
129
|
|
122
130
|
# Wakes a thread to allow the performer to execute a task.
|
123
|
-
# @param state [nil, Object] Contextual information for the performer. See {
|
131
|
+
# @param state [nil, Object] Contextual information for the performer. See {JobPerformer#next?}.
|
124
132
|
# @return [nil, Boolean] Whether work was started.
|
125
|
-
#
|
126
|
-
#
|
127
|
-
#
|
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.
|
128
137
|
def create_thread(state = nil)
|
129
|
-
return nil unless
|
138
|
+
return nil unless executor.running?
|
130
139
|
|
131
140
|
if state
|
132
|
-
return false unless
|
141
|
+
return false unless performer.next?(state)
|
133
142
|
|
134
143
|
if state[:scheduled_at]
|
135
144
|
scheduled_at = if state[:scheduled_at].is_a? String
|
@@ -144,18 +153,12 @@ module GoodJob # :nodoc:
|
|
144
153
|
delay ||= 0
|
145
154
|
run_now = delay <= 0.01
|
146
155
|
if run_now
|
147
|
-
return nil unless
|
156
|
+
return nil unless executor.ready_worker_count.positive?
|
148
157
|
elsif @max_cache.positive?
|
149
158
|
return nil unless remaining_cache_count.positive?
|
150
159
|
end
|
151
160
|
|
152
|
-
|
153
|
-
output = nil
|
154
|
-
Rails.application.executor.wrap { output = performer.next }
|
155
|
-
output
|
156
|
-
end
|
157
|
-
future.add_observer(self, :task_observer)
|
158
|
-
future.execute
|
161
|
+
create_task(delay)
|
159
162
|
|
160
163
|
run_now ? true : nil
|
161
164
|
end
|
@@ -169,45 +172,61 @@ module GoodJob # :nodoc:
|
|
169
172
|
create_task if output
|
170
173
|
end
|
171
174
|
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
@performer.next_at(
|
176
|
-
limit: @max_cache,
|
177
|
-
now_limit: @pool_options[:max_threads]
|
178
|
-
).each do |scheduled_at|
|
179
|
-
create_thread({ scheduled_at: scheduled_at })
|
180
|
-
end
|
181
|
-
end
|
182
|
-
|
175
|
+
# Information about the Scheduler
|
176
|
+
# @return [Hash]
|
183
177
|
def stats
|
184
178
|
{
|
185
|
-
name:
|
186
|
-
max_threads: @
|
187
|
-
active_threads: @
|
188
|
-
available_threads:
|
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,
|
189
183
|
max_cache: @max_cache,
|
190
184
|
active_cache: cache_count,
|
191
185
|
available_cache: remaining_cache_count,
|
192
186
|
}
|
193
187
|
end
|
194
188
|
|
189
|
+
# Preload existing runnable and future-scheduled jobs
|
190
|
+
# @return [void]
|
191
|
+
def warm_cache
|
192
|
+
return if @max_cache.zero?
|
193
|
+
|
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
|
203
|
+
end
|
204
|
+
|
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
|
212
|
+
end
|
213
|
+
|
195
214
|
private
|
196
215
|
|
197
|
-
attr_reader :timer_set
|
216
|
+
attr_reader :performer, :executor, :timer_set
|
198
217
|
|
199
|
-
def
|
200
|
-
instrument("scheduler_create_pool", { performer_name:
|
201
|
-
@timer_set =
|
202
|
-
@
|
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)
|
203
222
|
end
|
204
223
|
end
|
205
224
|
|
206
225
|
def create_task(delay = 0)
|
207
|
-
future = Concurrent::ScheduledTask.new(delay, args: [
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
211
230
|
end
|
212
231
|
future.add_observer(self, :task_observer)
|
213
232
|
future.execute
|
@@ -224,7 +243,7 @@ module GoodJob # :nodoc:
|
|
224
243
|
end
|
225
244
|
|
226
245
|
def cache_count
|
227
|
-
timer_set.
|
246
|
+
timer_set.length
|
228
247
|
end
|
229
248
|
|
230
249
|
def remaining_cache_count
|
@@ -249,5 +268,21 @@ module GoodJob # :nodoc:
|
|
249
268
|
end
|
250
269
|
end
|
251
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
|
252
287
|
end
|
253
288
|
end
|