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