good_job 1.1.2 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4117e28d2a899ebb8b66a17fa79ff8495b36521d92f9146059c7b78406a361b6
4
- data.tar.gz: 535dd8b9204cc76ee6749224217e5d33e23adc2414e8ca7c420d4deb35451efd
3
+ metadata.gz: 44da5dd6eb7dee7e08319f7405e97f81e4223f8c88d48ba4a0512fa006f89e78
4
+ data.tar.gz: fe3f5ca8a39b85b1aa4be77a819e910691223cf36e80c6ab337ff1b224a26e16
5
5
  SHA512:
6
- metadata.gz: 1f8bf7bf0f21604eb08b814d032b4a964f220b6d6384b368de7263672ece002f8fd01e5e2d77b0c6ed93e31ac03f235a2c40b8ad3f532de3fb3c755cd629ad24
7
- data.tar.gz: 6006611999250e6b7708f381302379f1a07ba3522151630b708388436c9264ea2e2b4277c5753027171e82e7dd8a82b6f272386ad39170a192b94d0b36e45111
6
+ metadata.gz: e6648e41c7ff99915716702651cd43fb7fffcff24528a9fb2d7e4a7913303d85d25c3b3f8ef54cc1b716a273836bc3c7fb2911750e1d99f216b2074b03f13ae8
7
+ data.tar.gz: '063048f09b76b10d4f38beb5fcf390d972f0952ed4df7b1053e8078da2bad005b816e21271026b95f8eaef12b8e3e1ce0a4cecf2cf40f965e852a8e2e9854aac'
@@ -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)
@@ -10,6 +10,7 @@ require 'good_job/multi_scheduler'
10
10
  require 'good_job/adapter'
11
11
  require 'good_job/pg_locks'
12
12
  require 'good_job/performer'
13
+ require 'good_job/current_execution'
13
14
 
14
15
  require 'active_job/queue_adapters/good_job_adapter'
15
16
 
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module GoodJob
2
- VERSION = '1.1.2'.freeze
2
+ VERSION = '1.1.3'.freeze
3
3
  end
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.2
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-13 00:00:00.000000000 Z
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