good_job 1.1.2 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -4
- data/lib/good_job.rb +1 -0
- data/lib/good_job/current_execution.rb +25 -0
- data/lib/good_job/job.rb +6 -1
- data/lib/good_job/railtie.rb +10 -0
- data/lib/good_job/scheduler.rb +55 -14
- data/lib/good_job/version.rb +1 -1
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 44da5dd6eb7dee7e08319f7405e97f81e4223f8c88d48ba4a0512fa006f89e78
|
4
|
+
data.tar.gz: fe3f5ca8a39b85b1aa4be77a819e910691223cf36e80c6ab337ff1b224a26e16
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e6648e41c7ff99915716702651cd43fb7fffcff24528a9fb2d7e4a7913303d85d25c3b3f8ef54cc1b716a273836bc3c7fb2911750e1d99f216b2074b03f13ae8
|
7
|
+
data.tar.gz: '063048f09b76b10d4f38beb5fcf390d972f0952ed4df7b1053e8078da2bad005b816e21271026b95f8eaef12b8e3e1ce0a4cecf2cf40f965e852a8e2e9854aac'
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.1.3](https://github.com/bensheldon/good_job/tree/v1.1.3) (2020-08-14)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.2...v1.1.3)
|
6
|
+
|
7
|
+
**Fixed bugs:**
|
8
|
+
|
9
|
+
- Job exceptions not properly attached to good\_jobs record [\#72](https://github.com/bensheldon/good_job/issues/72)
|
10
|
+
|
11
|
+
**Merged pull requests:**
|
12
|
+
|
13
|
+
- Capture errors via instrumentation from retry\_on and discard\_on [\#79](https://github.com/bensheldon/good_job/pull/79) ([bensheldon](https://github.com/bensheldon))
|
14
|
+
- Document GoodJob::Scheduler with Yard [\#78](https://github.com/bensheldon/good_job/pull/78) ([bensheldon](https://github.com/bensheldon))
|
15
|
+
|
3
16
|
## [v1.1.2](https://github.com/bensheldon/good_job/tree/v1.1.2) (2020-08-13)
|
4
17
|
|
5
18
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.1.1...v1.1.2)
|
@@ -60,6 +73,7 @@
|
|
60
73
|
|
61
74
|
- Re-perform a job if a StandardError bubbles up; better document job reliability [\#62](https://github.com/bensheldon/good_job/pull/62) ([bensheldon](https://github.com/bensheldon))
|
62
75
|
- Update the setup documentation to use correct bin setup command [\#61](https://github.com/bensheldon/good_job/pull/61) ([jm96441n](https://github.com/jm96441n))
|
76
|
+
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
63
77
|
|
64
78
|
## [v1.0.2](https://github.com/bensheldon/good_job/tree/v1.0.2) (2020-07-25)
|
65
79
|
|
@@ -90,10 +104,6 @@
|
|
90
104
|
|
91
105
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.2...v0.9.0)
|
92
106
|
|
93
|
-
**Merged pull requests:**
|
94
|
-
|
95
|
-
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
96
|
-
|
97
107
|
## [v0.8.2](https://github.com/bensheldon/good_job/tree/v0.8.2) (2020-07-18)
|
98
108
|
|
99
109
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.1...v0.8.2)
|
data/lib/good_job.rb
CHANGED
@@ -0,0 +1,25 @@
|
|
1
|
+
module GoodJob
|
2
|
+
# Thread-local attributes for passing values from Instrumentation.
|
3
|
+
# (Cannot use ActiveSupport::CurrentAttributes because ActiveJob resets it)
|
4
|
+
|
5
|
+
module CurrentExecution
|
6
|
+
# @!attribute [rw] error_on_retry
|
7
|
+
# @!scope class
|
8
|
+
# Error captured by retry_on
|
9
|
+
# @return [Exception, nil]
|
10
|
+
thread_mattr_accessor :error_on_retry
|
11
|
+
|
12
|
+
# @!attribute [rw] error_on_discard
|
13
|
+
# @!scope class
|
14
|
+
# Error captured by discard_on
|
15
|
+
# @return [Exception, nil]
|
16
|
+
thread_mattr_accessor :error_on_discard
|
17
|
+
|
18
|
+
# Resets attributes
|
19
|
+
# @return [void]
|
20
|
+
def self.reset
|
21
|
+
self.error_on_retry = nil
|
22
|
+
self.error_on_discard = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/good_job/job.rb
CHANGED
@@ -76,11 +76,11 @@ module GoodJob
|
|
76
76
|
def perform(destroy_after: !GoodJob.preserve_job_records, reperform_on_standard_error: GoodJob.reperform_jobs_on_standard_error)
|
77
77
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
78
78
|
|
79
|
+
GoodJob::CurrentExecution.reset
|
79
80
|
result = nil
|
80
81
|
rescued_error = nil
|
81
82
|
error = nil
|
82
83
|
|
83
|
-
ActiveSupport::Notifications.instrument("before_perform_job.good_job", { good_job: self })
|
84
84
|
self.performed_at = Time.current
|
85
85
|
save! unless destroy_after
|
86
86
|
|
@@ -96,11 +96,16 @@ module GoodJob
|
|
96
96
|
rescued_error = e
|
97
97
|
end
|
98
98
|
|
99
|
+
retry_or_discard_error = GoodJob::CurrentExecution.error_on_retry ||
|
100
|
+
GoodJob::CurrentExecution.error_on_discard
|
101
|
+
|
99
102
|
if rescued_error
|
100
103
|
error = rescued_error
|
101
104
|
elsif result.is_a?(Exception)
|
102
105
|
error = result
|
103
106
|
result = nil
|
107
|
+
elsif retry_or_discard_error
|
108
|
+
error = retry_or_discard_error
|
104
109
|
end
|
105
110
|
|
106
111
|
error_message = "#{error.class}: #{error.message}" if error
|
data/lib/good_job/railtie.rb
CHANGED
@@ -4,5 +4,15 @@ module GoodJob
|
|
4
4
|
ActiveSupport.on_load(:good_job) { self.logger = ::Rails.logger }
|
5
5
|
GoodJob::LogSubscriber.attach_to :good_job
|
6
6
|
end
|
7
|
+
|
8
|
+
initializer "good_job.active_job_notifications" do
|
9
|
+
ActiveSupport::Notifications.subscribe "enqueue_retry.active_job" do |event|
|
10
|
+
GoodJob::CurrentExecution.error_on_retry = event.payload[:error]
|
11
|
+
end
|
12
|
+
|
13
|
+
ActiveSupport::Notifications.subscribe "discard.active_job" do |event|
|
14
|
+
GoodJob::CurrentExecution.error_on_discard = event.payload[:error]
|
15
|
+
end
|
16
|
+
end
|
7
17
|
end
|
8
18
|
end
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -2,14 +2,22 @@ require "concurrent/executor/thread_pool_executor"
|
|
2
2
|
require "concurrent/timer_task"
|
3
3
|
require "concurrent/utility/processor_counter"
|
4
4
|
|
5
|
-
module GoodJob
|
5
|
+
module GoodJob # :nodoc:
|
6
|
+
# Schedulers are generic thread execution pools that are responsible for
|
7
|
+
# periodically checking for available execution tasks, executing tasks in a
|
8
|
+
# bounded thread-pool, and efficiently scaling execution threads.
|
9
|
+
#
|
10
|
+
# Schedulers are "generic" in the sense that they delegate task execution
|
11
|
+
# details to a "Performer" object that responds to #next.
|
6
12
|
class Scheduler
|
13
|
+
# Defaults for instance of Concurrent::TimerTask
|
7
14
|
DEFAULT_TIMER_OPTIONS = {
|
8
15
|
execution_interval: 1,
|
9
16
|
timeout_interval: 1,
|
10
17
|
run_now: true,
|
11
18
|
}.freeze
|
12
19
|
|
20
|
+
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
13
21
|
DEFAULT_POOL_OPTIONS = {
|
14
22
|
name: 'good_job',
|
15
23
|
min_threads: 0,
|
@@ -20,8 +28,15 @@ module GoodJob
|
|
20
28
|
fallback_policy: :discard,
|
21
29
|
}.freeze
|
22
30
|
|
31
|
+
# @!attribute [r] instances
|
32
|
+
# @!scope class
|
33
|
+
# All instantiated Schedulers in the current process.
|
34
|
+
# @return [array<GoodJob:Scheduler>]
|
23
35
|
cattr_reader :instances, default: [], instance_reader: false
|
24
36
|
|
37
|
+
# Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
|
38
|
+
# @param configuration [GoodJob::Configuration]
|
39
|
+
# @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
|
25
40
|
def self.from_configuration(configuration)
|
26
41
|
schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
|
27
42
|
queue_string, max_threads = queue_string_and_max_threads.split(':')
|
@@ -47,6 +62,9 @@ module GoodJob
|
|
47
62
|
end
|
48
63
|
end
|
49
64
|
|
65
|
+
# @param performer [GoodJob::Performer]
|
66
|
+
# @param timer_options [Hash] Options to instantiate a Concurrent::TimerTask
|
67
|
+
# @param pool_options [Hash] Options to instantiate a Concurrent::ThreadPoolExecutor
|
50
68
|
def initialize(performer, timer_options: {}, pool_options: {})
|
51
69
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
52
70
|
|
@@ -59,6 +77,9 @@ module GoodJob
|
|
59
77
|
create_pools
|
60
78
|
end
|
61
79
|
|
80
|
+
# Shut down the Scheduler.
|
81
|
+
# @param wait [Boolean] Wait for actively executing jobs to finish
|
82
|
+
# @return [void]
|
62
83
|
def shutdown(wait: true)
|
63
84
|
@_shutdown = true
|
64
85
|
|
@@ -76,10 +97,15 @@ module GoodJob
|
|
76
97
|
end
|
77
98
|
end
|
78
99
|
|
100
|
+
# True when the Scheduler is shutdown.
|
101
|
+
# @return [true, false, nil]
|
79
102
|
def shutdown?
|
80
103
|
@_shutdown
|
81
104
|
end
|
82
105
|
|
106
|
+
# Restart the Scheduler. When shutdown, start; or shutdown and start.
|
107
|
+
# @param wait [Boolean] Wait for actively executing jobs to finish
|
108
|
+
# @return [void]
|
83
109
|
def restart(wait: true)
|
84
110
|
ActiveSupport::Notifications.instrument("scheduler_restart_pools.good_job", { process_id: process_id }) do
|
85
111
|
shutdown(wait: wait) unless shutdown?
|
@@ -87,6 +113,8 @@ module GoodJob
|
|
87
113
|
end
|
88
114
|
end
|
89
115
|
|
116
|
+
# Triggers the execution the Performer, if an execution thread is available.
|
117
|
+
# @return [Boolean]
|
90
118
|
def create_thread
|
91
119
|
return false unless @pool.ready_worker_count.positive?
|
92
120
|
|
@@ -97,33 +125,29 @@ module GoodJob
|
|
97
125
|
end
|
98
126
|
future.add_observer(self, :task_observer)
|
99
127
|
future.execute
|
128
|
+
true
|
100
129
|
end
|
101
130
|
|
131
|
+
# Invoked on completion of TimerTask task.
|
132
|
+
# @!visibility private
|
133
|
+
# @return [void]
|
102
134
|
def timer_observer(time, executed_task, thread_error)
|
103
135
|
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
104
136
|
ActiveSupport::Notifications.instrument("finished_timer_task.good_job", { result: executed_task, error: thread_error, time: time })
|
105
137
|
end
|
106
138
|
|
139
|
+
# Invoked on completion of ThreadPoolExecutor task
|
140
|
+
# @!visibility private
|
141
|
+
# @return [void]
|
107
142
|
def task_observer(time, output, thread_error)
|
108
143
|
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
109
144
|
ActiveSupport::Notifications.instrument("finished_job_task.good_job", { result: output, error: thread_error, time: time })
|
110
145
|
create_thread if output
|
111
146
|
end
|
112
147
|
|
113
|
-
class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
|
114
|
-
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
115
|
-
def ready_worker_count
|
116
|
-
synchronize do
|
117
|
-
workers_still_to_be_created = @max_length - @pool.length
|
118
|
-
workers_created_but_waiting = @ready.length
|
119
|
-
|
120
|
-
workers_still_to_be_created + workers_created_but_waiting
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
148
|
private
|
126
149
|
|
150
|
+
# @return [void]
|
127
151
|
def create_pools
|
128
152
|
ActiveSupport::Notifications.instrument("scheduler_create_pools.good_job", { performer_name: @performer.name, max_threads: @pool_options[:max_threads], poll_interval: @timer_options[:execution_interval], process_id: process_id }) do
|
129
153
|
@pool = ThreadPoolExecutor.new(@pool_options)
|
@@ -135,12 +159,29 @@ module GoodJob
|
|
135
159
|
end
|
136
160
|
end
|
137
161
|
|
162
|
+
# @return [Integer] Current process ID
|
138
163
|
def process_id
|
139
164
|
Process.pid
|
140
165
|
end
|
141
166
|
|
167
|
+
# @return [String] Current thread name
|
142
168
|
def thread_name
|
143
|
-
Thread.current.name || Thread.current.object_id
|
169
|
+
(Thread.current.name || Thread.current.object_id).to_s
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Slightly customized sub-class of Concurrent::ThreadPoolExecutor
|
174
|
+
class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
|
175
|
+
# Number of idle or potential threads available to execute tasks
|
176
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
177
|
+
# @return [Integer]
|
178
|
+
def ready_worker_count
|
179
|
+
synchronize do
|
180
|
+
workers_still_to_be_created = @max_length - @pool.length
|
181
|
+
workers_created_but_waiting = @ready.length
|
182
|
+
|
183
|
+
workers_still_to_be_created + workers_created_but_waiting
|
184
|
+
end
|
144
185
|
end
|
145
186
|
end
|
146
187
|
end
|
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.1.
|
4
|
+
version: 1.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-08-
|
11
|
+
date: 2020-08-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -248,6 +248,20 @@ dependencies:
|
|
248
248
|
- - ">="
|
249
249
|
- !ruby/object:Gem::Version
|
250
250
|
version: '0'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: yard
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - ">="
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
258
|
+
type: :development
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - ">="
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '0'
|
251
265
|
description: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|
252
266
|
email:
|
253
267
|
- bensheldon@gmail.com
|
@@ -270,6 +284,7 @@ files:
|
|
270
284
|
- lib/good_job/adapter.rb
|
271
285
|
- lib/good_job/cli.rb
|
272
286
|
- lib/good_job/configuration.rb
|
287
|
+
- lib/good_job/current_execution.rb
|
273
288
|
- lib/good_job/job.rb
|
274
289
|
- lib/good_job/lockable.rb
|
275
290
|
- lib/good_job/log_subscriber.rb
|