good_job 0.8.2 → 0.9.0
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 +3 -2
- data/README.md +32 -3
- data/lib/good_job.rb +3 -1
- data/lib/good_job/cli.rb +19 -1
- data/lib/good_job/job.rb +57 -16
- data/lib/good_job/performer.rb +12 -0
- data/lib/good_job/scheduler.rb +8 -8
- data/lib/good_job/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bddc60fa297293af01b1334d4adf51981bfb8b314ba4577a7b3bff1d7385bd5a
|
4
|
+
data.tar.gz: 3533f21ffee4dba606558fa0fadd1f37a943aa285b4f6bebed4bb3b89045b61e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74c0d3df2df08e78d06baa8982401d633af536277231a17d87687ff3c7ba1553d4123f7c1ce49ae23094bd781db5ee23eb9584eeba94aae49fb8cc83da0c68e0
|
7
|
+
data.tar.gz: 7df20f03621cb66b864a2c2950e4a11f60199cd4954ad5a622a1631254fa04bab1243ee376567600a2aed294a8c14fa9802fd1024855dc7d473a7ff8f7166515
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
-
## [v0.
|
3
|
+
## [v0.9.0](https://github.com/bensheldon/good_job/tree/v0.9.0) (2020-07-20)
|
4
4
|
|
5
|
-
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.6.0...v0.
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.6.0...v0.9.0)
|
6
6
|
|
7
7
|
**Closed issues:**
|
8
8
|
|
@@ -11,6 +11,7 @@
|
|
11
11
|
|
12
12
|
**Merged pull requests:**
|
13
13
|
|
14
|
+
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
14
15
|
- Run Github Action tests on PRs from forks [\#44](https://github.com/bensheldon/good_job/pull/44) ([bensheldon](https://github.com/bensheldon))
|
15
16
|
- Fix Rubygems homepage URL [\#43](https://github.com/bensheldon/good_job/pull/43) ([joshmn](https://github.com/joshmn))
|
16
17
|
- Move where\(scheduled\_at: Time.current\) into dynamic part of GoodJob::Job::Performer [\#42](https://github.com/bensheldon/good_job/pull/42) ([bensheldon](https://github.com/bensheldon))
|
data/README.md
CHANGED
@@ -43,9 +43,12 @@ $ bundle install
|
|
43
43
|
t.integer :priority
|
44
44
|
t.jsonb :serialized_params
|
45
45
|
t.timestamp :scheduled_at
|
46
|
-
|
47
|
-
t.
|
48
|
-
t.
|
46
|
+
t.timestamp :performed_at
|
47
|
+
t.timestamp :finished_at
|
48
|
+
t.text :error
|
49
|
+
|
50
|
+
add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)"
|
51
|
+
add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)"
|
49
52
|
end
|
50
53
|
end
|
51
54
|
end
|
@@ -152,6 +155,32 @@ If your application is already using an ActiveJob backend, you will need to inst
|
|
152
155
|
|
153
156
|
1. Once you are confident that no unperformed jobs remain in the previous ActiveJob backend, code and configuration for that backend can be completely removed.
|
154
157
|
|
158
|
+
### Monitoring and preserving worked jobs
|
159
|
+
|
160
|
+
GoodJob is fully instrumented with [`ActiveSupport::Notifications`](https://edgeguides.rubyonrails.org/active_support_instrumentation.html#introduction-to-instrumentation).
|
161
|
+
|
162
|
+
By default, GoodJob will delete job records after they are run, regardless of whether they succeed or not (raising a kind of `StandardError`), unless they are interrupted (raising a kind of `Exception`).
|
163
|
+
|
164
|
+
To preserve job records for later inspection, set an initializer:
|
165
|
+
|
166
|
+
```ruby
|
167
|
+
# config/initializers/good_job.rb
|
168
|
+
GoodJob.preserve_job_records = true
|
169
|
+
```
|
170
|
+
|
171
|
+
It is also necessary to delete these preserved jobs from the database after a certain time period:
|
172
|
+
|
173
|
+
- For example, in a Rake task:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
# GoodJob::Job.finished(1.day.ago).delete_all
|
177
|
+
```
|
178
|
+
- For example, using the `good_job` command-line utility:
|
179
|
+
|
180
|
+
```bash
|
181
|
+
$ bundle exec good_job cleanup_preserved_jobs --before-seconds-ago=86400
|
182
|
+
```
|
183
|
+
|
155
184
|
## Development
|
156
185
|
|
157
186
|
To run tests:
|
data/lib/good_job.rb
CHANGED
@@ -4,13 +4,15 @@ require 'good_job/railtie'
|
|
4
4
|
require 'good_job/logging'
|
5
5
|
require 'good_job/lockable'
|
6
6
|
require 'good_job/job'
|
7
|
-
require
|
7
|
+
require 'good_job/scheduler'
|
8
8
|
require 'good_job/adapter'
|
9
9
|
require 'good_job/pg_locks'
|
10
|
+
require 'good_job/performer'
|
10
11
|
|
11
12
|
require 'active_job/queue_adapters/good_job_adapter'
|
12
13
|
|
13
14
|
module GoodJob
|
15
|
+
mattr_accessor :preserve_job_records, default: false
|
14
16
|
include Logging
|
15
17
|
|
16
18
|
ActiveSupport.run_load_hooks(:good_job, self)
|
data/lib/good_job/cli.rb
CHANGED
@@ -40,7 +40,12 @@ module GoodJob
|
|
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
42
|
|
43
|
-
|
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)
|
44
49
|
|
45
50
|
$stdout.puts "GoodJob worker starting with max_threads=#{max_threads} on queues=#{queue_names.join(',')}"
|
46
51
|
|
@@ -68,6 +73,19 @@ module GoodJob
|
|
68
73
|
$stdout.puts "GoodJob's jobs finished, exiting..."
|
69
74
|
end
|
70
75
|
|
76
|
+
desc :cleanup_preserved_jobs, "Delete preserved job records"
|
77
|
+
method_option :before_seconds_ago,
|
78
|
+
type: :numeric,
|
79
|
+
default: 24 * 60 * 60,
|
80
|
+
desc: "Delete records finished more than this many seconds ago"
|
81
|
+
def cleanup_preserved_jobs
|
82
|
+
require RAILS_ENVIRONMENT_RB
|
83
|
+
|
84
|
+
timestamp = Time.current - options[:before_seconds_ago]
|
85
|
+
result = GoodJob::Job.finished(timestamp).delete_all
|
86
|
+
$stdout.puts "Deleted #{result} preserved #{'job'.pluralize(result)} finished before #{timestamp}."
|
87
|
+
end
|
88
|
+
|
71
89
|
default_task :start
|
72
90
|
end
|
73
91
|
end
|
data/lib/good_job/job.rb
CHANGED
@@ -2,32 +2,46 @@ module GoodJob
|
|
2
2
|
class Job < ActiveRecord::Base
|
3
3
|
include Lockable
|
4
4
|
|
5
|
+
PreviouslyPerformedError = Class.new(StandardError)
|
6
|
+
|
5
7
|
DEFAULT_QUEUE_NAME = 'default'.freeze
|
6
8
|
DEFAULT_PRIORITY = 0
|
7
9
|
|
8
10
|
self.table_name = 'good_jobs'.freeze
|
9
11
|
|
12
|
+
scope :unfinished, (lambda do
|
13
|
+
if column_names.include?('finished_at')
|
14
|
+
where(finished_at: nil)
|
15
|
+
else
|
16
|
+
ActiveSupport::Deprecation.warn('GoodJob expects a good_jobs.finished_at column to exist. Please see the GoodJob README.md for migration instructions.')
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end)
|
10
20
|
scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(Time.current)).or(where(scheduled_at: nil)) }
|
11
21
|
scope :priority_ordered, -> { order(priority: :desc) }
|
12
|
-
scope :
|
22
|
+
scope :finished, ->(timestamp = nil) { timestamp ? where(arel_table['finished_at'].lteq(timestamp)) : where.not(finished_at: nil) }
|
13
23
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
24
|
+
def self.perform_with_advisory_lock(destroy_after: !GoodJob.preserve_job_records)
|
25
|
+
good_job = nil
|
26
|
+
result = nil
|
27
|
+
error = nil
|
28
|
+
|
29
|
+
unfinished.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
30
|
+
good_job = good_jobs.first
|
31
|
+
break unless good_job
|
18
32
|
|
19
|
-
|
20
|
-
|
33
|
+
result, error = good_job.perform(destroy_after: destroy_after)
|
34
|
+
end
|
21
35
|
|
22
|
-
|
23
|
-
|
24
|
-
break unless good_job
|
36
|
+
[good_job, result, error] if good_job
|
37
|
+
end
|
25
38
|
|
26
|
-
|
27
|
-
|
39
|
+
def self.perform_with_advisory_lock_and_preserve_job_records
|
40
|
+
perform_with_advisory_lock(destroy_after: false)
|
41
|
+
end
|
28
42
|
|
29
|
-
|
30
|
-
|
43
|
+
def self.perform_with_advisory_lock_and_destroy_job_records
|
44
|
+
perform_with_advisory_lock(destroy_after: true)
|
31
45
|
end
|
32
46
|
|
33
47
|
def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
|
@@ -50,16 +64,43 @@ module GoodJob
|
|
50
64
|
good_job
|
51
65
|
end
|
52
66
|
|
53
|
-
def perform
|
67
|
+
def perform(destroy_after: true)
|
68
|
+
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
69
|
+
|
70
|
+
result = nil
|
71
|
+
error = nil
|
72
|
+
|
54
73
|
ActiveSupport::Notifications.instrument("before_perform_job.good_job", { good_job: self })
|
74
|
+
self.performed_at = Time.current
|
75
|
+
save! unless destroy_after
|
76
|
+
|
55
77
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { good_job: self }) do
|
56
78
|
params = serialized_params.merge(
|
57
79
|
"provider_job_id" => id
|
58
80
|
)
|
59
|
-
|
81
|
+
begin
|
82
|
+
result = ActiveJob::Base.execute(params)
|
83
|
+
rescue StandardError => e
|
84
|
+
error = e
|
85
|
+
end
|
86
|
+
end
|
60
87
|
|
88
|
+
if error.nil? && result.is_a?(Exception)
|
89
|
+
error = result
|
90
|
+
result = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
error_message = "#{error.class}: #{error.message}" if error
|
94
|
+
self.error = error_message
|
95
|
+
self.finished_at = Time.current
|
96
|
+
|
97
|
+
if destroy_after
|
61
98
|
destroy!
|
99
|
+
else
|
100
|
+
save!
|
62
101
|
end
|
102
|
+
|
103
|
+
[result, error]
|
63
104
|
end
|
64
105
|
end
|
65
106
|
end
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -59,21 +59,21 @@ module GoodJob
|
|
59
59
|
|
60
60
|
def create_thread
|
61
61
|
future = Concurrent::Future.new(args: [@performer], executor: @pool) do |performer|
|
62
|
-
|
63
|
-
Rails.application.executor.wrap {
|
64
|
-
|
62
|
+
output = nil
|
63
|
+
Rails.application.executor.wrap { output = performer.next }
|
64
|
+
output
|
65
65
|
end
|
66
66
|
future.add_observer(self, :task_observer)
|
67
67
|
future.execute
|
68
68
|
end
|
69
69
|
|
70
|
-
def timer_observer(time, executed_task,
|
71
|
-
ActiveSupport::Notifications.instrument("finished_timer_task.good_job", { result: executed_task, error:
|
70
|
+
def timer_observer(time, executed_task, thread_error)
|
71
|
+
ActiveSupport::Notifications.instrument("finished_timer_task.good_job", { result: executed_task, error: thread_error, time: time })
|
72
72
|
end
|
73
73
|
|
74
|
-
def task_observer(time,
|
75
|
-
ActiveSupport::Notifications.instrument("finished_job_task.good_job", { result:
|
76
|
-
create_thread if
|
74
|
+
def task_observer(time, output, thread_error)
|
75
|
+
ActiveSupport::Notifications.instrument("finished_job_task.good_job", { result: output, error: thread_error, time: time })
|
76
|
+
create_thread if output
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
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: 0.
|
4
|
+
version: 0.9.0
|
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-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: concurrent-ruby
|
@@ -242,6 +242,7 @@ files:
|
|
242
242
|
- lib/good_job/job.rb
|
243
243
|
- lib/good_job/lockable.rb
|
244
244
|
- lib/good_job/logging.rb
|
245
|
+
- lib/good_job/performer.rb
|
245
246
|
- lib/good_job/pg_locks.rb
|
246
247
|
- lib/good_job/railtie.rb
|
247
248
|
- lib/good_job/scheduler.rb
|