good_job 1.9.1 → 1.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/good_job.rb +5 -0
- data/lib/good_job/cli.rb +1 -1
- data/lib/good_job/execution_result.rb +20 -0
- data/lib/good_job/job.rb +32 -36
- data/lib/good_job/notifier.rb +0 -1
- data/lib/good_job/poller.rb +0 -1
- data/lib/good_job/scheduler.rb +23 -12
- data/lib/good_job/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 94f195fb2a3c51544d58216d662caaf7fac989ec7cc5eb1fcb57b25a5e303a9d
|
4
|
+
data.tar.gz: 20a2b3ffcc844df543809df6bc143cdc1f42885bea28ffb8ec33c02bdf5e7d5b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2bb0c8fab29421100400e55959ac2ff95d63e0eec7876277b61d4913eb7f045e660d68ad4638d729e0cb42bbce4f18aea13a3c9e9daffd489659b72404f8d02
|
7
|
+
data.tar.gz: 2d8a691a8dae96a8154faa3b86e14a0fd25fd3fedaa35a3e5cedb69ac27bc1926eae4a84fcd9dc73f7f91e15eea9295be929346d711651d656c1425adf96b635
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.9.2](https://github.com/bensheldon/good_job/tree/v1.9.2) (2021-05-10)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.9.1...v1.9.2)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
|
9
|
+
- Run Scheduler\#warm\_cache operation in threadpool executor [\#242](https://github.com/bensheldon/good_job/pull/242) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- Jobs not visible in dashboard [\#245](https://github.com/bensheldon/good_job/issues/245)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- Use GoodJob::Job::ExecutionResult object instead of job execution returning an ordered array [\#241](https://github.com/bensheldon/good_job/pull/241) ([bensheldon](https://github.com/bensheldon))
|
18
|
+
- Update development dependencies [\#240](https://github.com/bensheldon/good_job/pull/240) ([bensheldon](https://github.com/bensheldon))
|
19
|
+
|
3
20
|
## [v1.9.1](https://github.com/bensheldon/good_job/tree/v1.9.1) (2021-04-19)
|
4
21
|
|
5
22
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.9.0...v1.9.1)
|
data/lib/good_job.rb
CHANGED
@@ -85,6 +85,11 @@ module GoodJob
|
|
85
85
|
# When forking processes you should shut down these background threads before forking, and restart them after forking.
|
86
86
|
# For example, you should use +shutdown+ and +restart+ when using async execution mode with Puma.
|
87
87
|
# See the {file:README.md#executing-jobs-async--in-process} for more explanation and examples.
|
88
|
+
# @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish
|
89
|
+
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
90
|
+
# * +-1+, the scheduler will wait until the shutdown is complete.
|
91
|
+
# * +0+, the scheduler will immediately shutdown and stop any active tasks.
|
92
|
+
# * +1..+, the scheduler will wait that many seconds before stopping any remaining active tasks.
|
88
93
|
# @param wait [Boolean] whether to wait for shutdown
|
89
94
|
# @return [void]
|
90
95
|
def self.shutdown(timeout: -1, wait: nil)
|
data/lib/good_job/cli.rb
CHANGED
@@ -83,7 +83,7 @@ module GoodJob
|
|
83
83
|
|
84
84
|
notifier = GoodJob::Notifier.new
|
85
85
|
poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
86
|
-
scheduler = GoodJob::Scheduler.from_configuration(configuration)
|
86
|
+
scheduler = GoodJob::Scheduler.from_configuration(configuration, warm_cache_on_initialize: true)
|
87
87
|
notifier.recipients << [scheduler, :create_thread]
|
88
88
|
poller.recipients << [scheduler, :create_thread]
|
89
89
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module GoodJob
|
2
|
+
# Stores the results of job execution
|
3
|
+
class ExecutionResult
|
4
|
+
# @return [Object, nil]
|
5
|
+
attr_reader :value
|
6
|
+
# @return [Exception, nil]
|
7
|
+
attr_reader :handled_error
|
8
|
+
# @return [Exception, nil]
|
9
|
+
attr_reader :unhandled_error
|
10
|
+
|
11
|
+
# @param value [Object, nil]
|
12
|
+
# @param handled_error [Exception, nil]
|
13
|
+
# @param unhandled_error [Exception, nil]
|
14
|
+
def initialize(value:, handled_error: nil, unhandled_error: nil)
|
15
|
+
@value = value
|
16
|
+
@handled_error = handled_error
|
17
|
+
@unhandled_error = unhandled_error
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/good_job/job.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module GoodJob
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
2
|
+
# ActiveRecord model that represents an +ActiveJob+ job.
|
3
|
+
# Parent class can be configured with +GoodJob.active_record_parent_class+.
|
4
|
+
# @!parse
|
5
|
+
# class Job < ActiveRecord::Base; end
|
5
6
|
class Job < Object.const_get(GoodJob.active_record_parent_class)
|
6
7
|
include Lockable
|
7
8
|
|
@@ -19,6 +20,7 @@ module GoodJob
|
|
19
20
|
|
20
21
|
# Parse a string representing a group of queues into a more readable data
|
21
22
|
# structure.
|
23
|
+
# @param string [String] Queue string
|
22
24
|
# @return [Hash]
|
23
25
|
# How to match a given queue. It can have the following keys and values:
|
24
26
|
# - +{ all: true }+ indicates that all queues match.
|
@@ -134,29 +136,26 @@ module GoodJob
|
|
134
136
|
|
135
137
|
# Finds the next eligible Job, acquire an advisory lock related to it, and
|
136
138
|
# executes the job.
|
137
|
-
# @return [
|
139
|
+
# @return [ExecutionResult, nil]
|
138
140
|
# If a job was executed, returns an array with the {Job} record, the
|
139
141
|
# return value for the job's +#perform+ method, and the exception the job
|
140
142
|
# raised, if any (if the job raised, then the second array entry will be
|
141
143
|
# +nil+). If there were no jobs to execute, returns +nil+.
|
142
144
|
def self.perform_with_advisory_lock
|
143
|
-
good_job = nil
|
144
|
-
result = nil
|
145
|
-
error = nil
|
146
|
-
|
147
145
|
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
148
146
|
good_job = good_jobs.first
|
149
147
|
# TODO: Determine why some records are fetched without an advisory lock at all
|
150
148
|
break unless good_job&.executable?
|
151
149
|
|
152
|
-
|
150
|
+
good_job.perform
|
153
151
|
end
|
154
|
-
|
155
|
-
[good_job, result, error] if good_job
|
156
152
|
end
|
157
153
|
|
158
154
|
# Fetches the scheduled execution time of the next eligible Job(s).
|
159
|
-
# @
|
155
|
+
# @param after [DateTime]
|
156
|
+
# @param limit [Integer]
|
157
|
+
# @param now_limit [Integer, nil]
|
158
|
+
# @return [Array<DateTime>]
|
160
159
|
def self.next_scheduled_at(after: nil, limit: 100, now_limit: nil)
|
161
160
|
query = advisory_unlocked.unfinished.schedule_ordered
|
162
161
|
|
@@ -182,7 +181,6 @@ module GoodJob
|
|
182
181
|
# @return [Job]
|
183
182
|
# The new {Job} instance representing the queued ActiveJob job.
|
184
183
|
def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
|
185
|
-
good_job = nil
|
186
184
|
ActiveSupport::Notifications.instrument("enqueue_job.good_job", { active_job: active_job, scheduled_at: scheduled_at, create_with_advisory_lock: create_with_advisory_lock }) do |instrument_payload|
|
187
185
|
good_job = GoodJob::Job.new(
|
188
186
|
queue_name: active_job.queue_name.presence || DEFAULT_QUEUE_NAME,
|
@@ -196,49 +194,37 @@ module GoodJob
|
|
196
194
|
|
197
195
|
good_job.save!
|
198
196
|
active_job.provider_job_id = good_job.id
|
199
|
-
end
|
200
197
|
|
201
|
-
|
198
|
+
good_job
|
199
|
+
end
|
202
200
|
end
|
203
201
|
|
204
202
|
# Execute the ActiveJob job this {Job} represents.
|
205
|
-
# @return [
|
203
|
+
# @return [ExecutionResult]
|
206
204
|
# An array of the return value of the job's +#perform+ method and the
|
207
205
|
# exception raised by the job, if any. If the job completed successfully,
|
208
206
|
# the second array entry (the exception) will be +nil+ and vice versa.
|
209
207
|
def perform
|
210
208
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
211
209
|
|
212
|
-
GoodJob::CurrentExecution.reset
|
213
|
-
|
214
210
|
self.performed_at = Time.current
|
215
211
|
save! if GoodJob.preserve_job_records
|
216
212
|
|
217
|
-
result
|
218
|
-
|
219
|
-
result_error = nil
|
220
|
-
if result.is_a?(Exception)
|
221
|
-
result_error = result
|
222
|
-
result = nil
|
223
|
-
end
|
224
|
-
|
225
|
-
job_error = unhandled_error ||
|
226
|
-
result_error ||
|
227
|
-
GoodJob::CurrentExecution.error_on_retry ||
|
228
|
-
GoodJob::CurrentExecution.error_on_discard
|
213
|
+
result = execute
|
229
214
|
|
215
|
+
job_error = result.handled_error || result.unhandled_error
|
230
216
|
self.error = "#{job_error.class}: #{job_error.message}" if job_error
|
231
217
|
|
232
|
-
if unhandled_error && GoodJob.retry_on_unhandled_error
|
218
|
+
if result.unhandled_error && GoodJob.retry_on_unhandled_error
|
233
219
|
save!
|
234
|
-
elsif GoodJob.preserve_job_records == true || (unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error)
|
220
|
+
elsif GoodJob.preserve_job_records == true || (result.unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error)
|
235
221
|
self.finished_at = Time.current
|
236
222
|
save!
|
237
223
|
else
|
238
224
|
destroy!
|
239
225
|
end
|
240
226
|
|
241
|
-
|
227
|
+
result
|
242
228
|
end
|
243
229
|
|
244
230
|
# Tests whether this job is safe to be executed by this thread.
|
@@ -249,16 +235,26 @@ module GoodJob
|
|
249
235
|
|
250
236
|
private
|
251
237
|
|
238
|
+
# @return [GoodJob::ExecutionResult]
|
252
239
|
def execute
|
253
240
|
params = serialized_params.merge(
|
254
241
|
"provider_job_id" => id
|
255
242
|
)
|
256
243
|
|
244
|
+
GoodJob::CurrentExecution.reset
|
257
245
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self, process_id: GoodJob::CurrentExecution.process_id, thread_name: GoodJob::CurrentExecution.thread_name }) do
|
258
|
-
|
246
|
+
value = ActiveJob::Base.execute(params)
|
247
|
+
|
248
|
+
if value.is_a?(Exception)
|
249
|
+
handled_error = value
|
250
|
+
value = nil
|
251
|
+
end
|
252
|
+
handled_error ||= GoodJob::CurrentExecution.error_on_retry || GoodJob::CurrentExecution.error_on_discard
|
253
|
+
|
254
|
+
ExecutionResult.new(value: value, handled_error: handled_error)
|
255
|
+
rescue StandardError => e
|
256
|
+
ExecutionResult.new(value: nil, unhandled_error: e)
|
259
257
|
end
|
260
|
-
rescue StandardError => e
|
261
|
-
[nil, e]
|
262
258
|
end
|
263
259
|
end
|
264
260
|
end
|
data/lib/good_job/notifier.rb
CHANGED
@@ -75,7 +75,6 @@ module GoodJob # :nodoc:
|
|
75
75
|
# This stops the background LISTENing thread.
|
76
76
|
# Use {#shutdown?} to determine whether threads have stopped.
|
77
77
|
# @param timeout [nil, Numeric] Seconds to wait for active threads.
|
78
|
-
#
|
79
78
|
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
80
79
|
# * +-1+, the scheduler will wait until the shutdown is complete.
|
81
80
|
# * +0+, the scheduler will immediately shutdown and stop any threads.
|
data/lib/good_job/poller.rb
CHANGED
@@ -54,7 +54,6 @@ module GoodJob # :nodoc:
|
|
54
54
|
# Shut down the notifier.
|
55
55
|
# Use {#shutdown?} to determine whether threads have stopped.
|
56
56
|
# @param timeout [nil, Numeric] Seconds to wait for active threads.
|
57
|
-
#
|
58
57
|
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
59
58
|
# * +-1+, the scheduler will wait until the shutdown is complete.
|
60
59
|
# * +0+, the scheduler will immediately shutdown and stop any threads.
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -37,7 +37,7 @@ module GoodJob # :nodoc:
|
|
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
|
@@ -93,7 +93,6 @@ module GoodJob # :nodoc:
|
|
93
93
|
# This stops all threads in the thread pool.
|
94
94
|
# Use {#shutdown?} to determine whether threads have stopped.
|
95
95
|
# @param timeout [nil, Numeric] Seconds to wait for actively executing jobs to finish
|
96
|
-
#
|
97
96
|
# * +nil+, the scheduler will trigger a shutdown but not wait for it to complete.
|
98
97
|
# * +-1+, the scheduler will wait until the shutdown is complete.
|
99
98
|
# * +0+, the scheduler will immediately shutdown and stop any active tasks.
|
@@ -192,12 +191,24 @@ module GoodJob # :nodoc:
|
|
192
191
|
def warm_cache
|
193
192
|
return if @max_cache.zero?
|
194
193
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
200
208
|
end
|
209
|
+
future.add_observer(observer, :call)
|
210
|
+
|
211
|
+
future.execute
|
201
212
|
end
|
202
213
|
|
203
214
|
private
|
@@ -213,9 +224,9 @@ module GoodJob # :nodoc:
|
|
213
224
|
|
214
225
|
def create_task(delay = 0)
|
215
226
|
future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
|
216
|
-
|
217
|
-
|
218
|
-
|
227
|
+
Rails.application.executor.wrap do
|
228
|
+
thr_performer.next
|
229
|
+
end
|
219
230
|
end
|
220
231
|
future.add_observer(self, :task_observer)
|
221
232
|
future.execute
|
data/lib/good_job/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.9.
|
4
|
+
version: 1.9.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -344,6 +344,7 @@ files:
|
|
344
344
|
- lib/good_job/configuration.rb
|
345
345
|
- lib/good_job/current_execution.rb
|
346
346
|
- lib/good_job/daemon.rb
|
347
|
+
- lib/good_job/execution_result.rb
|
347
348
|
- lib/good_job/job.rb
|
348
349
|
- lib/good_job/job_performer.rb
|
349
350
|
- lib/good_job/lockable.rb
|