good_job 1.0.2 → 1.0.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: 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