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 +4 -4
- data/CHANGELOG.md +19 -2
- data/README.md +66 -6
- data/lib/good_job.rb +1 -0
- data/lib/good_job/cli.rb +1 -7
- data/lib/good_job/job.rb +24 -23
- data/lib/good_job/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc7b3997aaa38583efb4c91e66a2a5f9b8bfd75d4c14c9baaf8f1af7c236a42e
|
4
|
+
data.tar.gz: 285b441a5e5add440142960884370a1b74168266252fa48ce569cbb56e80f240
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f54e83d6ac19d80da2123a395ccf6ffd7eb2faab8e68bd5d50a572c8f0bf64fbc907125a47b7ff91dccac5497af113d043bba4f62ee6ae10fecaf3c004a1114d
|
7
|
+
data.tar.gz: 0b65a60bde26db0cfcfa55f166e80043bcbccb6e2c80f5de1840d2deae3ee28880c904bb53ffc2478ad834ffef8cfe567cee9a179932e20e4f92fdf8504838e4
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,23 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [v1.0.
|
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
|
-
###
|
82
|
+
### Error handling, retries, and reliability
|
83
83
|
|
84
|
-
|
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
|
-
|
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/
|
230
|
+
$ bin/setup
|
171
231
|
|
172
232
|
# Run the tests
|
173
233
|
$ bin/rspec
|
data/lib/good_job.rb
CHANGED
@@ -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)
|
data/lib/good_job/cli.rb
CHANGED
@@ -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
|
|
data/lib/good_job/job.rb
CHANGED
@@ -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
|
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
|
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:
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
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
|
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]
|
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.0.
|
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-
|
11
|
+
date: 2020-07-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|