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 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