good_job 1.9.1 → 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 +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
|