good_job 1.0.2 → 1.0.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: f7c64e8766a52e6bbbb5e55b409bb0100e54790d6d88340893e61f930cbf8b9e
4
- data.tar.gz: 7e89b7bf2d3aaa45cc5b07deab36f5334a61cac44c0aec06206ddbd6eb0ebc22
3
+ metadata.gz: fc7b3997aaa38583efb4c91e66a2a5f9b8bfd75d4c14c9baaf8f1af7c236a42e
4
+ data.tar.gz: 285b441a5e5add440142960884370a1b74168266252fa48ce569cbb56e80f240
5
5
  SHA512:
6
- metadata.gz: 944b64f713ad584b56fe386dc63aa6afc0538e498cff4fb46aec9098306b7e436a3d29cebd968157f34e62c9b1b0ce932e15967b4a631f9f04c60ca43d77f4c8
7
- data.tar.gz: 5000ffc97bc381cb94999478c1fe90202b681d5cf87d697f4700cc60b94a7417fd67ca3f5428c4b50acb195bde7850a3a5d809e32c5baaf08ae3a58a2101624a
6
+ metadata.gz: f54e83d6ac19d80da2123a395ccf6ffd7eb2faab8e68bd5d50a572c8f0bf64fbc907125a47b7ff91dccac5497af113d043bba4f62ee6ae10fecaf3c004a1114d
7
+ data.tar.gz: 0b65a60bde26db0cfcfa55f166e80043bcbccb6e2c80f5de1840d2deae3ee28880c904bb53ffc2478ad834ffef8cfe567cee9a179932e20e4f92fdf8504838e4
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
 
3
- ## [v1.0.2](https://github.com/bensheldon/good_job/tree/v1.0.2) (2020-07-24)
3
+ ## [v1.0.3](https://github.com/bensheldon/good_job/tree/v1.0.3) (2020-07-25)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.2...v1.0.3)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Preserve GoodJob::Jobs when a StandardError is raised [\#60](https://github.com/bensheldon/good_job/issues/60)
10
+
11
+ **Closed issues:**
12
+
13
+ - Have an initial setup generator [\#6](https://github.com/bensheldon/good_job/issues/6)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - 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))
18
+ - Update the setup documentation to use correct bin setup command [\#61](https://github.com/bensheldon/good_job/pull/61) ([jm96441n](https://github.com/jm96441n))
19
+
20
+ ## [v1.0.2](https://github.com/bensheldon/good_job/tree/v1.0.2) (2020-07-25)
4
21
 
5
22
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.0.1...v1.0.2)
6
23
 
@@ -96,6 +113,7 @@
96
113
  - Add configuration options to good\_job executable [\#33](https://github.com/bensheldon/good_job/pull/33) ([bensheldon](https://github.com/bensheldon))
97
114
  - Extract Job querying behavior out of Scheduler [\#31](https://github.com/bensheldon/good_job/pull/31) ([bensheldon](https://github.com/bensheldon))
98
115
  - Allow configuration of Rails queue adapter with `:good\_job` [\#28](https://github.com/bensheldon/good_job/pull/28) ([bensheldon](https://github.com/bensheldon))
116
+ - Update development Ruby to 2.6.5 [\#22](https://github.com/bensheldon/good_job/pull/22) ([bensheldon](https://github.com/bensheldon))
99
117
 
100
118
  ## [v0.5.0](https://github.com/bensheldon/good_job/tree/v0.5.0) (2020-07-13)
101
119
 
@@ -120,7 +138,6 @@
120
138
 
121
139
  **Merged pull requests:**
122
140
 
123
- - Update development Ruby to 2.6.5 [\#22](https://github.com/bensheldon/good_job/pull/22) ([bensheldon](https://github.com/bensheldon))
124
141
  - Simplify the internal API, removing JobWrapper and InlineScheduler [\#21](https://github.com/bensheldon/good_job/pull/21) ([bensheldon](https://github.com/bensheldon))
125
142
  - Generate a new future for every executed job [\#20](https://github.com/bensheldon/good_job/pull/20) ([bensheldon](https://github.com/bensheldon))
126
143
  - Configuration for maximum number of job execution threads [\#18](https://github.com/bensheldon/good_job/pull/18) ([bensheldon](https://github.com/bensheldon))
data/README.md CHANGED
@@ -79,25 +79,85 @@ $ bundle install
79
79
  # [--poll-interval=N] # Interval between polls for available jobs in seconds (default: 1)
80
80
  ```
81
81
 
82
- ### Taking advantage of ActiveJob
82
+ ### Error handling, retries, and reliability
83
83
 
84
- ActiveJob has a rich set of built-in functionality for timeouts, error handling, and retrying. For example:
84
+ GoodJob guarantees _at-least-once_ performance of jobs. GoodJob fully supports ActiveJob's built-in functionality for error handling, retries and timeouts.
85
+
86
+ #### Error handling
87
+
88
+ By default, if a job raises an error while it is being performed, _and it bubbles up to the GoodJob backend_, GoodJob will be immediately re-perform the job until it finishes successfully.
89
+
90
+ - `Exception`-type errors, such as a SIGINT, will always cause a job to be re-performed.
91
+ - `StandardError`-type errors, by default, will cause a job to be re-performed, though this is configurable:
92
+
93
+ ```ruby
94
+ # config/initializers/good_job.rb
95
+ GoodJob.reperform_jobs_on_standard_error = true # => default
96
+ ```
97
+
98
+ ### Retrying jobs
99
+
100
+ ActiveJob can be configured to retry an infinite number of times, with an exponential backoff. Using ActiveJob's `retry_on` will ensure that errors do not bubble up to the GoodJob backend:
85
101
 
86
102
  ```ruby
87
103
  class ApplicationJob < ActiveJob::Base
88
- # Retry errors an infinite number of times with exponential back-off
89
104
  retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
105
+ # ...
106
+ end
107
+ ```
108
+
109
+ When specifying a limited number of retries, care must be taken to ensure that an error does not bubble up to the GoodJob backend because that will result in the job being re-performed:
110
+
111
+ ```ruby
112
+ class ApplicationJob < ActiveJob::Base
113
+ retry_on StandardError, attempts: 5 do |_job, _exception|
114
+ # Log error, etc.
115
+ # You must implement this block, otherwise,
116
+ # Active Job will re-raise the error.
117
+ # Do not re-raise the error, otherwise
118
+ # GoodJob will immediately re-perform the job.
119
+ end
120
+ # ...
121
+ end
122
+ ```
123
+
124
+ GoodJob can be configured to allow omitting `retry_on`'s block argument and implicitly discard un-handled errors:
90
125
 
91
- # Timeout jobs after 10 minutes
126
+ ```ruby
127
+ # config/initializers/good_job.rb
128
+
129
+ # Do NOT re-perform a job if a StandardError bubbles up to the GoodJob backend
130
+ GoodJob.reperform_jobs_on_standard_error = false
131
+ ```
132
+
133
+ ActiveJob's `discard_on` functionality is supported too.
134
+
135
+ #### ActionMailer retries
136
+
137
+ Using a Mailer's `#deliver_later` will enqueue an instance of `ActionMailer::DeliveryJob` which inherits from `ActiveJob::Base` rather than your applications `ApplicationJob`. You can use an initializer to configure retries on `ActionMailer::DeliveryJob`:
138
+
139
+ ```ruby
140
+ # config/initializers/good_job.rb
141
+ ActionMailer::DeliveryJob.retry_on StandardError, wait: :exponentially_longer, attempts: Float::INFINITY
142
+ ```
143
+
144
+ #### Timeouts
145
+
146
+ Job timeouts can be configured with an `around_perform`:
147
+
148
+ ```ruby
149
+ class ApplicationJob < ActiveJob::Base
92
150
  JobTimeoutError = Class.new(StandardError)
151
+
93
152
  around_perform do |_job, block|
153
+ # Timeout jobs after 10 minutes
94
154
  Timeout.timeout(10.minutes, JobTimeoutError) do
95
155
  block.call
96
156
  end
97
157
  end
98
158
  end
99
159
  ```
100
-
160
+
101
161
  ### Configuring Job Execution Threads
102
162
 
103
163
  GoodJob executes enqueued jobs using threads. There is a lot than can be said about [multithreaded behavior in Ruby on Rails](https://guides.rubyonrails.org/threading_and_code_execution.html), but briefly:
@@ -167,7 +227,7 @@ To run tests:
167
227
  $ git clone git@github.com:bensheldon/good_job.git
168
228
 
169
229
  # Set up the local environment
170
- $ bin/setup_test
230
+ $ bin/setup
171
231
 
172
232
  # Run the tests
173
233
  $ bin/rspec
@@ -13,6 +13,7 @@ require 'active_job/queue_adapters/good_job_adapter'
13
13
 
14
14
  module GoodJob
15
15
  mattr_accessor :preserve_job_records, default: false
16
+ mattr_accessor :reperform_jobs_on_standard_error, default: true
16
17
  include Logging
17
18
 
18
19
  ActiveSupport.run_load_hooks(:good_job, self)
@@ -39,13 +39,7 @@ module GoodJob
39
39
  job_query = GoodJob::Job.all.priority_ordered
40
40
  queue_names_without_all = queue_names.reject { |q| q == '*' }
41
41
  job_query = job_query.where(queue_name: queue_names_without_all) unless queue_names_without_all.size.zero?
42
-
43
- performer_method = if GoodJob.preserve_job_records
44
- :perform_with_advisory_lock_and_preserve_job_records
45
- else
46
- :perform_with_advisory_lock_and_destroy_job_records
47
- end
48
- job_performer = GoodJob::Performer.new(job_query, performer_method)
42
+ job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock)
49
43
 
50
44
  $stdout.puts "GoodJob worker starting with max_threads=#{max_threads} on queues=#{queue_names.join(',')}"
51
45
 
@@ -21,7 +21,7 @@ module GoodJob
21
21
  scope :priority_ordered, -> { order(priority: :desc) }
22
22
  scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
23
23
 
24
- def self.perform_with_advisory_lock(destroy_after: !GoodJob.preserve_job_records)
24
+ def self.perform_with_advisory_lock
25
25
  good_job = nil
26
26
  result = nil
27
27
  error = nil
@@ -30,20 +30,12 @@ module GoodJob
30
30
  good_job = good_jobs.first
31
31
  break unless good_job
32
32
 
33
- result, error = good_job.perform(destroy_after: destroy_after)
33
+ result, error = good_job.perform
34
34
  end
35
35
 
36
36
  [good_job, result, error] if good_job
37
37
  end
38
38
 
39
- def self.perform_with_advisory_lock_and_preserve_job_records
40
- perform_with_advisory_lock(destroy_after: false)
41
- end
42
-
43
- def self.perform_with_advisory_lock_and_destroy_job_records
44
- perform_with_advisory_lock(destroy_after: true)
45
- end
46
-
47
39
  def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
48
40
  good_job = nil
49
41
  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|
@@ -64,40 +56,49 @@ module GoodJob
64
56
  good_job
65
57
  end
66
58
 
67
- def perform(destroy_after: true)
59
+ def perform(destroy_after: !GoodJob.preserve_job_records, reperform_on_standard_error: GoodJob.reperform_jobs_on_standard_error)
68
60
  raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
69
61
 
70
62
  result = nil
63
+ rescued_error = nil
71
64
  error = nil
72
65
 
73
66
  ActiveSupport::Notifications.instrument("before_perform_job.good_job", { good_job: self })
74
67
  self.performed_at = Time.current
75
68
  save! unless destroy_after
76
69
 
77
- ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
78
- params = serialized_params.merge(
79
- "provider_job_id" => id
80
- )
81
- begin
70
+ params = serialized_params.merge(
71
+ "provider_job_id" => id
72
+ )
73
+
74
+ begin
75
+ ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
82
76
  result = ActiveJob::Base.execute(params)
83
- rescue StandardError => e
84
- error = e
85
77
  end
78
+ rescue StandardError => e
79
+ rescued_error = e
86
80
  end
87
81
 
88
- if error.nil? && result.is_a?(Exception)
82
+ if rescued_error
83
+ error = rescued_error
84
+ elsif result.is_a?(Exception)
89
85
  error = result
90
86
  result = nil
91
87
  end
92
88
 
93
89
  error_message = "#{error.class}: #{error.message}" if error
94
90
  self.error = error_message
95
- self.finished_at = Time.current
96
91
 
97
- if destroy_after
98
- destroy!
99
- else
92
+ if rescued_error && reperform_on_standard_error
100
93
  save!
94
+ else
95
+ self.finished_at = Time.current
96
+
97
+ if destroy_after
98
+ destroy!
99
+ else
100
+ save!
101
+ end
101
102
  end
102
103
 
103
104
  [result, error]
@@ -1,3 +1,3 @@
1
1
  module GoodJob
2
- VERSION = '1.0.2'.freeze
2
+ VERSION = '1.0.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.0.2
4
+ version: 1.0.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-07-25 00:00:00.000000000 Z
11
+ date: 2020-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby