good_job 1.2.6 → 1.3.4
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 +66 -5
- data/README.md +6 -3
- data/engine/app/controllers/good_job/active_jobs_controller.rb +1 -1
- data/engine/app/controllers/good_job/dashboards_controller.rb +52 -3
- data/engine/app/views/good_job/dashboards/index.html.erb +26 -3
- data/engine/app/views/layouts/good_job/base.html.erb +2 -2
- data/engine/app/views/shared/_jobs_table.erb +3 -3
- data/lib/good_job.rb +18 -2
- data/lib/good_job/adapter.rb +3 -0
- data/lib/good_job/cli.rb +5 -2
- data/lib/good_job/configuration.rb +1 -1
- data/lib/good_job/job.rb +1 -1
- data/lib/good_job/lockable.rb +7 -7
- data/lib/good_job/log_subscriber.rb +10 -10
- data/lib/good_job/multi_scheduler.rb +14 -5
- data/lib/good_job/notifier.rb +3 -3
- data/lib/good_job/poller.rb +94 -0
- data/lib/good_job/scheduler.rb +27 -62
- data/lib/good_job/version.rb +1 -1
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c7d64f21fbabba859d20d0a277d0a963672fcfde1284881b0130b1e05e62252
|
4
|
+
data.tar.gz: b7dbc95191a2bf115176f0e8ce09b8f7c997a2cb852fb673e5fee17c241434be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0e58bf60a8008617703b575d55cdefd94fa9405d8d7cb034a75013b3a3fc8e4205833cf818d03caa137470b9ff431258d19e166e5d56f83a70667c3c484d904
|
7
|
+
data.tar.gz: adc2a494ea2f4e3e4ac7f569b013edade8a223f1e54474835cba34c6ac6233760f94bbbffb62f0ed37a119f8f15dfd1310a87b304483bbae72ecd0129f10f439
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,69 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.3.4](https://github.com/bensheldon/good_job/tree/v1.3.4) (2020-12-02)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.3...v1.3.4)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Fix job ordering. [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
|
10
|
+
|
11
|
+
## [v1.3.3](https://github.com/bensheldon/good_job/tree/v1.3.3) (2020-12-01)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.2...v1.3.3)
|
14
|
+
|
15
|
+
**Merged pull requests:**
|
16
|
+
|
17
|
+
- UI: Admin UI with filters and space efficient layout [\#173](https://github.com/bensheldon/good_job/pull/173) ([zealot128](https://github.com/zealot128))
|
18
|
+
|
19
|
+
## [v1.3.2](https://github.com/bensheldon/good_job/tree/v1.3.2) (2020-11-12)
|
20
|
+
|
21
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.1...v1.3.2)
|
22
|
+
|
23
|
+
**Fixed bugs:**
|
24
|
+
|
25
|
+
- \(bug\) MultiScheduler polling bug [\#171](https://github.com/bensheldon/good_job/issues/171)
|
26
|
+
- MultiScheduler should delegate to all schedulers when state is nil [\#172](https://github.com/bensheldon/good_job/pull/172) ([bensheldon](https://github.com/bensheldon))
|
27
|
+
|
28
|
+
## [v1.3.1](https://github.com/bensheldon/good_job/tree/v1.3.1) (2020-11-01)
|
29
|
+
|
30
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.0...v1.3.1)
|
31
|
+
|
32
|
+
**Implemented enhancements:**
|
33
|
+
|
34
|
+
- Extract polling from scheduler into Polling object [\#128](https://github.com/bensheldon/good_job/issues/128)
|
35
|
+
- Format serialized params to ease reading [\#170](https://github.com/bensheldon/good_job/pull/170) ([morgoth](https://github.com/morgoth))
|
36
|
+
|
37
|
+
**Fixed bugs:**
|
38
|
+
|
39
|
+
- Don't disconnect a nil activerecord connection [\#161](https://github.com/bensheldon/good_job/pull/161) ([bensheldon](https://github.com/bensheldon))
|
40
|
+
|
41
|
+
**Closed issues:**
|
42
|
+
|
43
|
+
- Propose addition of GoodJob to queue-shootout benchmarks [\#40](https://github.com/bensheldon/good_job/issues/40)
|
44
|
+
|
45
|
+
**Merged pull requests:**
|
46
|
+
|
47
|
+
- Ensure Rails is a development dependency [\#169](https://github.com/bensheldon/good_job/pull/169) ([bensheldon](https://github.com/bensheldon))
|
48
|
+
- Fix Ruby 2.7 GH action by setting default bundler explicitly [\#166](https://github.com/bensheldon/good_job/pull/166) ([bensheldon](https://github.com/bensheldon))
|
49
|
+
- Cache ruby version explicitly in Github Action [\#165](https://github.com/bensheldon/good_job/pull/165) ([bensheldon](https://github.com/bensheldon))
|
50
|
+
- Update development dependencies, rubocop [\#164](https://github.com/bensheldon/good_job/pull/164) ([bensheldon](https://github.com/bensheldon))
|
51
|
+
- Fix intended constant hierarchy of GoodJob::Scheduler::ThreadPoolExecutor [\#158](https://github.com/bensheldon/good_job/pull/158) ([bensheldon](https://github.com/bensheldon))
|
52
|
+
- Add bin/test\_app executable for Rails debugging [\#157](https://github.com/bensheldon/good_job/pull/157) ([bensheldon](https://github.com/bensheldon))
|
53
|
+
- Extract Scheduler polling behavior to its own object [\#152](https://github.com/bensheldon/good_job/pull/152) ([bensheldon](https://github.com/bensheldon))
|
54
|
+
|
55
|
+
## [v1.3.0](https://github.com/bensheldon/good_job/tree/v1.3.0) (2020-10-03)
|
56
|
+
|
57
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.6...v1.3.0)
|
58
|
+
|
59
|
+
**Implemented enhancements:**
|
60
|
+
|
61
|
+
- Lengthen default poll interval from 1 to 5 seconds [\#156](https://github.com/bensheldon/good_job/pull/156) ([bensheldon](https://github.com/bensheldon))
|
62
|
+
|
63
|
+
**Merged pull requests:**
|
64
|
+
|
65
|
+
- Rename reperform\_jobs\_on\_standard\_error to retry\_on\_unhandled\_error [\#154](https://github.com/bensheldon/good_job/pull/154) ([morgoth](https://github.com/morgoth))
|
66
|
+
|
3
67
|
## [v1.2.6](https://github.com/bensheldon/good_job/tree/v1.2.6) (2020-09-29)
|
4
68
|
|
5
69
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.5...v1.2.6)
|
@@ -57,7 +121,6 @@
|
|
57
121
|
- Correct example on how to configure multiple queues by command line. [\#135](https://github.com/bensheldon/good_job/pull/135) ([morgoth](https://github.com/morgoth))
|
58
122
|
- Update ActionMailer Job class, to match the default [\#130](https://github.com/bensheldon/good_job/pull/130) ([morgoth](https://github.com/morgoth))
|
59
123
|
- Add initial Engine scaffold [\#125](https://github.com/bensheldon/good_job/pull/125) ([bensheldon](https://github.com/bensheldon))
|
60
|
-
- Zeitwerk Loader Implementation [\#123](https://github.com/bensheldon/good_job/pull/123) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
61
124
|
- Update code-level documentation [\#111](https://github.com/bensheldon/good_job/pull/111) ([bensheldon](https://github.com/bensheldon))
|
62
125
|
|
63
126
|
## [v1.2.4](https://github.com/bensheldon/good_job/tree/v1.2.4) (2020-09-01)
|
@@ -81,6 +144,7 @@
|
|
81
144
|
|
82
145
|
**Merged pull requests:**
|
83
146
|
|
147
|
+
- Zeitwerk Loader Implementation [\#123](https://github.com/bensheldon/good_job/pull/123) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
84
148
|
- Remove unused PgLocks class [\#120](https://github.com/bensheldon/good_job/pull/120) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
85
149
|
- Fix readme CommandLine option links [\#115](https://github.com/bensheldon/good_job/pull/115) ([gadimbaylisahil](https://github.com/gadimbaylisahil))
|
86
150
|
- Have YARD render markdown files with GFM \(Github Flavored Markdown\) [\#113](https://github.com/bensheldon/good_job/pull/113) ([bensheldon](https://github.com/bensheldon))
|
@@ -270,10 +334,6 @@
|
|
270
334
|
|
271
335
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.2...v0.9.0)
|
272
336
|
|
273
|
-
**Merged pull requests:**
|
274
|
-
|
275
|
-
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
276
|
-
|
277
337
|
## [v0.8.2](https://github.com/bensheldon/good_job/tree/v0.8.2) (2020-07-18)
|
278
338
|
|
279
339
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v0.8.1...v0.8.2)
|
@@ -301,6 +361,7 @@
|
|
301
361
|
|
302
362
|
**Merged pull requests:**
|
303
363
|
|
364
|
+
- Allow preservation of finished job records [\#46](https://github.com/bensheldon/good_job/pull/46) ([bensheldon](https://github.com/bensheldon))
|
304
365
|
- Replace Adapter inline boolean kwarg with execution\_mode instead [\#41](https://github.com/bensheldon/good_job/pull/41) ([bensheldon](https://github.com/bensheldon))
|
305
366
|
|
306
367
|
## [v0.7.0](https://github.com/bensheldon/good_job/tree/v0.7.0) (2020-07-16)
|
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# GoodJob
|
2
2
|
|
3
|
+
[](https://rubygems.org/gems/good_job)
|
4
|
+
[](https://github.com/bensheldon/good_job/actions)
|
5
|
+
|
3
6
|
GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
|
4
7
|
|
5
8
|
**Inspired by [Delayed::Job](https://github.com/collectiveidea/delayed_job) and [Que](https://github.com/que-rb/que), GoodJob is designed for maximum compatibility with Ruby on Rails, ActiveJob, and Postgres to be simple and performant for most workloads.**
|
@@ -210,7 +213,7 @@ Good Job’s general behavior can also be configured via several attributes dire
|
|
210
213
|
|
211
214
|
- **`GoodJob.logger`** ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger`.
|
212
215
|
- **`GoodJob.preserve_job_records`** (boolean) keeps job records in your database even after jobs are completed. (Default: `false`)
|
213
|
-
- **`GoodJob.
|
216
|
+
- **`GoodJob.retry_on_unhandled_error`** (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `true`)
|
214
217
|
- **`GoodJob.on_thread_error`** (proc, lambda, or callable) will be called when an Exception. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake.
|
215
218
|
|
216
219
|
You’ll generally want to configure these in `config/initializers/good_job.rb`, like so:
|
@@ -218,7 +221,7 @@ You’ll generally want to configure these in `config/initializers/good_job.rb`,
|
|
218
221
|
```ruby
|
219
222
|
# config/initializers/good_job.rb
|
220
223
|
GoodJob.preserve_job_records = true
|
221
|
-
GoodJob.
|
224
|
+
GoodJob.retry_on_unhandled_error = false
|
222
225
|
GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
|
223
226
|
```
|
224
227
|
|
@@ -301,7 +304,7 @@ When using `retry_on` with _a limited number of retries_, the final exception wi
|
|
301
304
|
|
302
305
|
```ruby
|
303
306
|
# config/initializers/good_job.rb
|
304
|
-
GoodJob.
|
307
|
+
GoodJob.retry_on_unhandled_error = false
|
305
308
|
```
|
306
309
|
|
307
310
|
Alternatively, pass a block to `retry_on` to handle the final exception instead of raising it to GoodJob:
|
@@ -2,7 +2,7 @@ module GoodJob
|
|
2
2
|
class ActiveJobsController < GoodJob::BaseController
|
3
3
|
def show
|
4
4
|
@jobs = GoodJob::Job.where("serialized_params ->> 'job_id' = ?", params[:id])
|
5
|
-
.order(
|
5
|
+
.order(Arel.sql("COALESCE(scheduled_at, created_at) DESC"))
|
6
6
|
end
|
7
7
|
end
|
8
8
|
end
|
@@ -1,10 +1,59 @@
|
|
1
1
|
module GoodJob
|
2
2
|
class DashboardsController < GoodJob::BaseController
|
3
|
-
|
4
|
-
|
3
|
+
class JobFilter
|
4
|
+
attr_accessor :params
|
5
|
+
|
6
|
+
def initialize(params)
|
7
|
+
@params = params
|
8
|
+
end
|
9
|
+
|
10
|
+
def last
|
11
|
+
@_last ||= jobs.last
|
12
|
+
end
|
13
|
+
|
14
|
+
def jobs
|
15
|
+
sql = GoodJob::Job.display_all(after_scheduled_at: params[:after_scheduled_at], after_id: params[:after_id])
|
5
16
|
.limit(params.fetch(:limit, 10))
|
17
|
+
if params[:job_class] # rubocop:disable Style/IfUnlessModifier
|
18
|
+
sql = sql.where("serialized_params->>'job_class' = ?", params[:job_class])
|
19
|
+
end
|
20
|
+
if params[:state]
|
21
|
+
case params[:state]
|
22
|
+
when 'finished'
|
23
|
+
sql = sql.finished
|
24
|
+
when 'unfinished'
|
25
|
+
sql = sql.unfinished
|
26
|
+
when 'errors'
|
27
|
+
sql = sql.where.not(error: nil)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
sql
|
31
|
+
end
|
32
|
+
|
33
|
+
def states
|
34
|
+
{
|
35
|
+
'finished' => GoodJob::Job.finished.count,
|
36
|
+
'unfinished' => GoodJob::Job.unfinished.count,
|
37
|
+
'errors' => GoodJob::Job.where.not(error: nil).count,
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def job_classes
|
42
|
+
GoodJob::Job.group("serialized_params->>'job_class'").count
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_query(override)
|
46
|
+
{
|
47
|
+
state: params[:state],
|
48
|
+
job_class: params[:job_class],
|
49
|
+
}.merge(override).delete_if { |_, v| v.nil? }.to_query
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def index
|
54
|
+
@filter = JobFilter.new(params)
|
6
55
|
|
7
|
-
job_data = GoodJob::Job.connection.exec_query Arel.sql(<<~SQL)
|
56
|
+
job_data = GoodJob::Job.connection.exec_query Arel.sql(<<~SQL.squish)
|
8
57
|
SELECT *
|
9
58
|
FROM generate_series(
|
10
59
|
date_trunc('hour', NOW() - '1 day'::interval),
|
@@ -2,13 +2,36 @@
|
|
2
2
|
<%= render 'shared/chart', chart_data: @chart %>
|
3
3
|
</div>
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
<div class='card mb-2'>
|
6
|
+
<div class='card-body d-flex flex-wrap'>
|
7
|
+
<div class='mr-4'>
|
8
|
+
<small>Filter by job class</small>
|
9
|
+
<br>
|
10
|
+
<% @filter.job_classes.each do |name, count| %>
|
11
|
+
<a href='<%= request.path + "?#{@filter.to_query(job_class: name)}" %>' class='btn btn-sm btn-outline-secondary <%= "active" if params[:job_class] == name %>'>
|
12
|
+
<%= name %> (<%= count %>)
|
13
|
+
</a>
|
14
|
+
<% end %>
|
15
|
+
</div>
|
16
|
+
<div>
|
17
|
+
<small>Filter by state</small>
|
18
|
+
<br>
|
19
|
+
<% @filter.states.each do |name, count| %>
|
20
|
+
<a href='<%= request.path + "?#{@filter.to_query(state: name)}" %>' class='btn btn-sm btn-outline-secondary <%= "active" if params[:state] == name %>'>
|
21
|
+
<%= name %> (<%= count %>)
|
22
|
+
</a>
|
23
|
+
<% end %>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
</div>
|
27
|
+
|
28
|
+
<% if @filter.jobs.present? %>
|
29
|
+
<%= render 'shared/jobs_table', jobs: @filter.jobs %>
|
7
30
|
|
8
31
|
<nav aria-label="Job pagination">
|
9
32
|
<ul class="pagination">
|
10
33
|
<li class="page-item">
|
11
|
-
<%= link_to({ after_scheduled_at: (@
|
34
|
+
<%= link_to({ after_scheduled_at: (@filter.last.scheduled_at || @filter.last.created_at), after_id: @filter.last.id }, class: "page-link") do %>
|
12
35
|
Next jobs <span aria-hidden="true">»</span>
|
13
36
|
<% end %>
|
14
37
|
</li>
|
@@ -18,7 +18,7 @@
|
|
18
18
|
</head>
|
19
19
|
<body>
|
20
20
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
21
|
-
<div class="container">
|
21
|
+
<div class="container-fluid">
|
22
22
|
<%= link_to "GoodJob 👍", root_path, class: 'navbar-brand mb-0 h1' %>
|
23
23
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
24
24
|
<span class="navbar-toggler-icon"></span>
|
@@ -48,7 +48,7 @@
|
|
48
48
|
</div>
|
49
49
|
</nav>
|
50
50
|
|
51
|
-
<div class="container">
|
51
|
+
<div class="container-fluid">
|
52
52
|
<div class="card border-warning text-dark my-3">
|
53
53
|
<div class="card-body">
|
54
54
|
<p class="card-text">🚧 GoodJob's dashboard is a work in progress. Please contribute ideas and code on <a href="https://github.com/bensheldon/good_job/issues" target="_blank" rel="nofollow noopener noreferrer">Github</a>.</p>
|
@@ -1,13 +1,13 @@
|
|
1
1
|
<div class="table-responsive">
|
2
|
-
<table class="table table-bordered table-hover">
|
2
|
+
<table class="table table-bordered table-hover table-sm">
|
3
3
|
<thead>
|
4
4
|
<th>GoodJob ID</th>
|
5
5
|
<th>ActiveJob ID</th>
|
6
6
|
<th>Job Class</th>
|
7
7
|
<th>Queue</th>
|
8
8
|
<th>Scheduled At</th>
|
9
|
-
<th>ActiveJob Params</th>
|
10
9
|
<th>Error</th>
|
10
|
+
<th>ActiveJob Params</th>
|
11
11
|
</thead>
|
12
12
|
<tbody>
|
13
13
|
<% jobs.each do |job| %>
|
@@ -17,8 +17,8 @@
|
|
17
17
|
<td><%= job.serialized_params['job_class'] %></td>
|
18
18
|
<td><%= job.queue_name %></td>
|
19
19
|
<td><%= job.scheduled_at || job.created_at %></td>
|
20
|
-
<td><%= job.serialized_params %></td>
|
21
20
|
<td><%= job.error %></td>
|
21
|
+
<td><pre><%= JSON.pretty_generate(job.serialized_params) %></pre></td>
|
22
22
|
</tr>
|
23
23
|
<% end %>
|
24
24
|
</tbody>
|
data/lib/good_job.rb
CHANGED
@@ -37,14 +37,30 @@ module GoodJob
|
|
37
37
|
# @return [Boolean]
|
38
38
|
mattr_accessor :preserve_job_records, default: false
|
39
39
|
|
40
|
-
# @!attribute [rw]
|
40
|
+
# @!attribute [rw] retry_on_unhandled_error
|
41
41
|
# @!scope class
|
42
42
|
# Whether to re-perform a job when a type of +StandardError+ is raised to GoodJob (default: +true+).
|
43
43
|
# If +true+, causes jobs to be re-queued and retried if they raise an instance of +StandardError+.
|
44
44
|
# If +false+, jobs will be discarded or marked as finished if they raise an instance of +StandardError+.
|
45
45
|
# Instances of +Exception+, like +SIGINT+, will *always* be retried, regardless of this attribute's value.
|
46
46
|
# @return [Boolean]
|
47
|
-
mattr_accessor :
|
47
|
+
mattr_accessor :retry_on_unhandled_error, default: true
|
48
|
+
|
49
|
+
# @deprecated Use {GoodJob#retry_on_unhandled_error} instead.
|
50
|
+
def self.reperform_jobs_on_standard_error
|
51
|
+
ActiveSupport::Deprecation.warn(
|
52
|
+
"Calling 'GoodJob.reperform_jobs_on_standard_error' is deprecated. Please use 'retry_on_unhandled_error'"
|
53
|
+
)
|
54
|
+
retry_on_unhandled_error
|
55
|
+
end
|
56
|
+
|
57
|
+
# @deprecated Use {GoodJob#retry_on_unhandled_error=} instead.
|
58
|
+
def self.reperform_jobs_on_standard_error=(value)
|
59
|
+
ActiveSupport::Deprecation.warn(
|
60
|
+
"Setting 'GoodJob.reperform_jobs_on_standard_error=' is deprecated. Please use 'retry_on_unhandled_error='"
|
61
|
+
)
|
62
|
+
self.retry_on_unhandled_error = value
|
63
|
+
end
|
48
64
|
|
49
65
|
# @!attribute [rw] on_thread_error
|
50
66
|
# @!scope class
|
data/lib/good_job/adapter.rb
CHANGED
@@ -43,8 +43,10 @@ module GoodJob
|
|
43
43
|
|
44
44
|
if @execution_mode == :async # rubocop:disable Style/GuardClause
|
45
45
|
@notifier = notifier || GoodJob::Notifier.new
|
46
|
+
@poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
46
47
|
@scheduler = scheduler || GoodJob::Scheduler.from_configuration(configuration)
|
47
48
|
@notifier.recipients << [@scheduler, :create_thread]
|
49
|
+
@poller.recipients << [@scheduler, :create_thread]
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|
@@ -88,6 +90,7 @@ module GoodJob
|
|
88
90
|
# @return [void]
|
89
91
|
def shutdown(wait: true)
|
90
92
|
@notifier&.shutdown(wait: wait)
|
93
|
+
@poller&.shutdown(wait: wait)
|
91
94
|
@scheduler&.shutdown(wait: wait)
|
92
95
|
end
|
93
96
|
|
data/lib/good_job/cli.rb
CHANGED
@@ -42,14 +42,16 @@ module GoodJob
|
|
42
42
|
method_option :poll_interval,
|
43
43
|
type: :numeric,
|
44
44
|
banner: 'SECONDS',
|
45
|
-
desc: "Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default:
|
45
|
+
desc: "Interval between polls for available jobs in seconds (env var: GOOD_JOB_POLL_INTERVAL, default: 5)"
|
46
46
|
def start
|
47
47
|
set_up_application!
|
48
|
+
configuration = GoodJob::Configuration.new(options)
|
48
49
|
|
49
50
|
notifier = GoodJob::Notifier.new
|
50
|
-
|
51
|
+
poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
51
52
|
scheduler = GoodJob::Scheduler.from_configuration(configuration)
|
52
53
|
notifier.recipients << [scheduler, :create_thread]
|
54
|
+
poller.recipients << [scheduler, :create_thread]
|
53
55
|
|
54
56
|
@stop_good_job_executable = false
|
55
57
|
%w[INT TERM].each do |signal|
|
@@ -62,6 +64,7 @@ module GoodJob
|
|
62
64
|
end
|
63
65
|
|
64
66
|
notifier.shutdown
|
67
|
+
poller.shutdown
|
65
68
|
scheduler.shutdown
|
66
69
|
end
|
67
70
|
|
@@ -8,7 +8,7 @@ module GoodJob
|
|
8
8
|
# Default number of threads to use per {Scheduler}
|
9
9
|
DEFAULT_MAX_THREADS = 5
|
10
10
|
# Default number of seconds between polls for jobs
|
11
|
-
DEFAULT_POLL_INTERVAL =
|
11
|
+
DEFAULT_POLL_INTERVAL = 5
|
12
12
|
# Default number of seconds to preserve jobs for {CLI#cleanup_preserved_jobs}
|
13
13
|
DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO = 24 * 60 * 60
|
14
14
|
|
data/lib/good_job/job.rb
CHANGED
@@ -204,7 +204,7 @@ module GoodJob
|
|
204
204
|
|
205
205
|
self.error = "#{job_error.class}: #{job_error.message}" if job_error
|
206
206
|
|
207
|
-
if unhandled_error && GoodJob.
|
207
|
+
if unhandled_error && GoodJob.retry_on_unhandled_error
|
208
208
|
save!
|
209
209
|
elsif GoodJob.preserve_job_records == true || (unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error)
|
210
210
|
self.finished_at = Time.current
|
data/lib/good_job/lockable.rb
CHANGED
@@ -56,7 +56,7 @@ module GoodJob
|
|
56
56
|
# @example Get the records that have a session awaiting a lock:
|
57
57
|
# MyLockableRecord.joins_advisory_locks.where("pg_locks.granted = ?", false)
|
58
58
|
scope :joins_advisory_locks, (lambda do
|
59
|
-
join_sql = <<~SQL
|
59
|
+
join_sql = <<~SQL.squish
|
60
60
|
LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
|
61
61
|
AND pg_locks.objsubid = 1
|
62
62
|
AND pg_locks.classid = ('x' || substr(md5(:table_name || #{quoted_table_name}.#{quoted_primary_key}::text), 1, 16))::bit(32)::int
|
@@ -140,10 +140,10 @@ module GoodJob
|
|
140
140
|
# all remaining locks).
|
141
141
|
# @return [Boolean] whether the lock was acquired.
|
142
142
|
def advisory_lock
|
143
|
-
where_sql = <<~SQL
|
143
|
+
where_sql = <<~SQL.squish
|
144
144
|
pg_try_advisory_lock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
145
145
|
SQL
|
146
|
-
self.class.unscoped.
|
146
|
+
self.class.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }])
|
147
147
|
end
|
148
148
|
|
149
149
|
# Releases an advisory lock on this record if it is locked by this database
|
@@ -151,10 +151,10 @@ module GoodJob
|
|
151
151
|
# {#advisory_unlock} and {#advisory_lock} the same number of times.
|
152
152
|
# @return [Boolean] whether the lock was released.
|
153
153
|
def advisory_unlock
|
154
|
-
where_sql = <<~SQL
|
154
|
+
where_sql = <<~SQL.squish
|
155
155
|
pg_advisory_unlock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
156
156
|
SQL
|
157
|
-
self.class.unscoped.
|
157
|
+
self.class.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }])
|
158
158
|
end
|
159
159
|
|
160
160
|
# Acquires an advisory lock on this record or raises
|
@@ -191,13 +191,13 @@ module GoodJob
|
|
191
191
|
# Tests whether this record has an advisory lock on it.
|
192
192
|
# @return [Boolean]
|
193
193
|
def advisory_locked?
|
194
|
-
self.class.unscoped.advisory_locked.
|
194
|
+
self.class.unscoped.advisory_locked.exists?(id: send(self.class.primary_key))
|
195
195
|
end
|
196
196
|
|
197
197
|
# Tests whether this record is locked by the current database session.
|
198
198
|
# @return [Boolean]
|
199
199
|
def owns_advisory_lock?
|
200
|
-
self.class.unscoped.owns_advisory_locked.
|
200
|
+
self.class.unscoped.owns_advisory_locked.exists?(id: send(self.class.primary_key))
|
201
201
|
end
|
202
202
|
|
203
203
|
# Releases all advisory locks on the record that are held by the current
|
@@ -45,14 +45,13 @@ module GoodJob
|
|
45
45
|
end
|
46
46
|
|
47
47
|
# @macro notification_responder
|
48
|
-
def
|
48
|
+
def scheduler_create_pool(event)
|
49
49
|
max_threads = event.payload[:max_threads]
|
50
|
-
poll_interval = event.payload[:poll_interval]
|
51
50
|
performer_name = event.payload[:performer_name]
|
52
51
|
process_id = event.payload[:process_id]
|
53
52
|
|
54
53
|
info(tags: [process_id]) do
|
55
|
-
"GoodJob started scheduler with queues=#{performer_name} max_threads=#{max_threads}
|
54
|
+
"GoodJob started scheduler with queues=#{performer_name} max_threads=#{max_threads}."
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
@@ -166,12 +165,12 @@ module GoodJob
|
|
166
165
|
# @return [Logger]
|
167
166
|
def logger
|
168
167
|
@_logger ||= begin
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
168
|
+
logger = Logger.new(StringIO.new)
|
169
|
+
loggers.each do |each_logger|
|
170
|
+
logger.extend(ActiveSupport::Logger.broadcast(each_logger))
|
171
|
+
end
|
172
|
+
logger
|
173
|
+
end
|
175
174
|
end
|
176
175
|
|
177
176
|
# Reset {LogSubscriber.logger} and force it to rebuild a new shortcut to
|
@@ -192,11 +191,12 @@ module GoodJob
|
|
192
191
|
# @return [void]
|
193
192
|
def tag_logger(*tags, &block)
|
194
193
|
tags = tags.dup.unshift("GoodJob").compact
|
194
|
+
good_job_tag = ["ActiveJob"].freeze
|
195
195
|
|
196
196
|
self.class.loggers.inject(block) do |inner, each_logger|
|
197
197
|
if each_logger.respond_to?(:tagged)
|
198
198
|
tags_for_logger = if each_logger.formatter.current_tags.include?("ActiveJob")
|
199
|
-
|
199
|
+
good_job_tag + tags
|
200
200
|
else
|
201
201
|
tags
|
202
202
|
end
|
@@ -26,14 +26,23 @@ module GoodJob
|
|
26
26
|
# Delegates to {Scheduler#create_thread}.
|
27
27
|
def create_thread(state = nil)
|
28
28
|
results = []
|
29
|
-
|
30
|
-
|
29
|
+
|
30
|
+
if state
|
31
|
+
schedulers.any? do |scheduler|
|
32
|
+
scheduler.create_thread(state).tap { |result| results << result }
|
33
|
+
end
|
34
|
+
else
|
35
|
+
schedulers.each do |scheduler|
|
36
|
+
results << scheduler.create_thread(state)
|
37
|
+
end
|
31
38
|
end
|
32
39
|
|
33
|
-
if
|
40
|
+
if results.any?
|
34
41
|
true
|
35
|
-
|
36
|
-
|
42
|
+
elsif results.any? { |result| result == false }
|
43
|
+
false
|
44
|
+
else # rubocop:disable Style/EmptyElse
|
45
|
+
nil
|
37
46
|
end
|
38
47
|
end
|
39
48
|
end
|
data/lib/good_job/notifier.rb
CHANGED
@@ -34,7 +34,7 @@ module GoodJob # :nodoc:
|
|
34
34
|
# @param message [#to_json]
|
35
35
|
def self.notify(message)
|
36
36
|
connection = ActiveRecord::Base.connection
|
37
|
-
connection.exec_query <<~SQL
|
37
|
+
connection.exec_query <<~SQL.squish
|
38
38
|
NOTIFY #{CHANNEL}, #{connection.quote(message.to_json)}
|
39
39
|
SQL
|
40
40
|
end
|
@@ -75,7 +75,7 @@ module GoodJob # :nodoc:
|
|
75
75
|
# If +wait+ is +true+, the notifier will wait for background thread to shutdown.
|
76
76
|
# If +wait+ is +false+, this method will return immediately even though threads may still be running.
|
77
77
|
# Use {#shutdown?} to determine whether threads have stopped.
|
78
|
-
# @param wait [Boolean] Wait for actively executing
|
78
|
+
# @param wait [Boolean] Wait for actively executing threads to finish
|
79
79
|
# @return [void]
|
80
80
|
def shutdown(wait: true)
|
81
81
|
return unless @pool.running?
|
@@ -147,7 +147,7 @@ module GoodJob # :nodoc:
|
|
147
147
|
pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
|
148
148
|
yield pg_conn
|
149
149
|
ensure
|
150
|
-
ar_conn
|
150
|
+
ar_conn&.disconnect!
|
151
151
|
end
|
152
152
|
end
|
153
153
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'concurrent/atomic/atomic_boolean'
|
2
|
+
|
3
|
+
module GoodJob # :nodoc:
|
4
|
+
#
|
5
|
+
# Pollers regularly wake up execution threads to check for new work.
|
6
|
+
#
|
7
|
+
class Poller
|
8
|
+
# Defaults for instance of Concurrent::TimerTask.
|
9
|
+
# The timer controls how and when sleeping threads check for new work.
|
10
|
+
DEFAULT_TIMER_OPTIONS = {
|
11
|
+
execution_interval: Configuration::DEFAULT_POLL_INTERVAL,
|
12
|
+
timeout_interval: 1,
|
13
|
+
run_now: true,
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
# @!attribute [r] instances
|
17
|
+
# @!scope class
|
18
|
+
# List of all instantiated Pollers in the current process.
|
19
|
+
# @return [array<GoodJob:Poller>]
|
20
|
+
cattr_reader :instances, default: [], instance_reader: false
|
21
|
+
|
22
|
+
def self.from_configuration(configuration)
|
23
|
+
GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
24
|
+
end
|
25
|
+
|
26
|
+
# List of recipients that will receive notifications.
|
27
|
+
# @return [Array<#call, Array(Object, Symbol)>]
|
28
|
+
attr_reader :recipients
|
29
|
+
|
30
|
+
# @param recipients [Array<#call, Array(Object, Symbol)>]
|
31
|
+
# @param poll_interval [Hash] number of seconds between polls
|
32
|
+
def initialize(*recipients, poll_interval: nil)
|
33
|
+
@recipients = Concurrent::Array.new(recipients)
|
34
|
+
|
35
|
+
@timer_options = DEFAULT_TIMER_OPTIONS.dup
|
36
|
+
@timer_options[:execution_interval] = poll_interval if poll_interval.present?
|
37
|
+
|
38
|
+
self.class.instances << self
|
39
|
+
|
40
|
+
create_pool
|
41
|
+
end
|
42
|
+
|
43
|
+
# Shut down the poller.
|
44
|
+
# If +wait+ is +true+, the poller will wait for background thread to shutdown.
|
45
|
+
# If +wait+ is +false+, this method will return immediately even though threads may still be running.
|
46
|
+
# Use {#shutdown?} to determine whether threads have stopped.
|
47
|
+
# @param wait [Boolean] Wait for actively executing threads to finish
|
48
|
+
# @return [void]
|
49
|
+
def shutdown(wait: true)
|
50
|
+
return unless @timer&.running?
|
51
|
+
|
52
|
+
@timer.shutdown
|
53
|
+
@timer.wait_for_termination if wait
|
54
|
+
end
|
55
|
+
|
56
|
+
# Tests whether the poller is shutdown.
|
57
|
+
# @return [true, false, nil]
|
58
|
+
def shutdown?
|
59
|
+
!@timer&.running?
|
60
|
+
end
|
61
|
+
|
62
|
+
# Restart the poller.
|
63
|
+
# When shutdown, start; or shutdown and start.
|
64
|
+
# @param wait [Boolean] Wait for background thread to finish
|
65
|
+
# @return [void]
|
66
|
+
def restart(wait: true)
|
67
|
+
shutdown(wait: wait)
|
68
|
+
create_pool
|
69
|
+
end
|
70
|
+
|
71
|
+
# Invoked on completion of TimerTask task.
|
72
|
+
# @!visibility private
|
73
|
+
# @return [void]
|
74
|
+
def timer_observer(time, executed_task, thread_error)
|
75
|
+
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
76
|
+
instrument("finished_timer_task", { result: executed_task, error: thread_error, time: time })
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def create_pool
|
82
|
+
return if @timer_options[:execution_interval] <= 0
|
83
|
+
|
84
|
+
@timer = Concurrent::TimerTask.new(@timer_options) do
|
85
|
+
recipients.each do |recipient|
|
86
|
+
target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
|
87
|
+
target.send(method_name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
@timer.add_observer(self, :timer_observer)
|
91
|
+
@timer.execute
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -26,14 +26,6 @@ module GoodJob # :nodoc:
|
|
26
26
|
fallback_policy: :discard,
|
27
27
|
}.freeze
|
28
28
|
|
29
|
-
# Defaults for instance of Concurrent::TimerTask.
|
30
|
-
# The timer controls how and when sleeping threads check for new work.
|
31
|
-
DEFAULT_TIMER_OPTIONS = {
|
32
|
-
execution_interval: Configuration::DEFAULT_POLL_INTERVAL,
|
33
|
-
timeout_interval: 1,
|
34
|
-
run_now: true,
|
35
|
-
}.freeze
|
36
|
-
|
37
29
|
# @!attribute [r] instances
|
38
30
|
# @!scope class
|
39
31
|
# List of all instantiated Schedulers in the current process.
|
@@ -41,7 +33,6 @@ module GoodJob # :nodoc:
|
|
41
33
|
cattr_reader :instances, default: [], instance_reader: false
|
42
34
|
|
43
35
|
# Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
|
44
|
-
# TODO: move this to GoodJob::Configuration
|
45
36
|
# @param configuration [GoodJob::Configuration]
|
46
37
|
# @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
|
47
38
|
def self.from_configuration(configuration)
|
@@ -53,7 +44,7 @@ module GoodJob # :nodoc:
|
|
53
44
|
parsed = GoodJob::Job.queue_parser(queue_string)
|
54
45
|
job_filter = proc do |state|
|
55
46
|
if parsed[:exclude]
|
56
|
-
|
47
|
+
parsed[:exclude].exclude?(state[:queue_name])
|
57
48
|
elsif parsed[:include]
|
58
49
|
parsed[:include].include? state[:queue_name]
|
59
50
|
else
|
@@ -62,7 +53,7 @@ module GoodJob # :nodoc:
|
|
62
53
|
end
|
63
54
|
job_performer = GoodJob::Performer.new(job_query, :perform_with_advisory_lock, name: queue_string, filter: job_filter)
|
64
55
|
|
65
|
-
GoodJob::Scheduler.new(job_performer, max_threads: max_threads
|
56
|
+
GoodJob::Scheduler.new(job_performer, max_threads: max_threads)
|
66
57
|
end
|
67
58
|
|
68
59
|
if schedulers.size > 1
|
@@ -73,23 +64,19 @@ module GoodJob # :nodoc:
|
|
73
64
|
end
|
74
65
|
|
75
66
|
# @param performer [GoodJob::Performer]
|
76
|
-
# @param max_threads [Numeric, nil]
|
77
|
-
|
78
|
-
def initialize(performer, max_threads: nil, poll_interval: nil)
|
67
|
+
# @param max_threads [Numeric, nil] number of seconds between polls for jobs
|
68
|
+
def initialize(performer, max_threads: nil)
|
79
69
|
raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
|
80
70
|
|
81
71
|
self.class.instances << self
|
82
72
|
|
83
73
|
@performer = performer
|
84
74
|
|
85
|
-
@timer_options = DEFAULT_TIMER_OPTIONS.dup
|
86
|
-
@timer_options[:execution_interval] = poll_interval if poll_interval.present?
|
87
|
-
|
88
75
|
@pool_options = DEFAULT_POOL_OPTIONS.dup
|
89
76
|
@pool_options[:max_threads] = max_threads if max_threads.present?
|
90
|
-
@pool_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@pool_options[:max_threads]}
|
77
|
+
@pool_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@pool_options[:max_threads]})"
|
91
78
|
|
92
|
-
|
79
|
+
create_pool
|
93
80
|
end
|
94
81
|
|
95
82
|
# Shut down the scheduler.
|
@@ -100,28 +87,20 @@ module GoodJob # :nodoc:
|
|
100
87
|
# @param wait [Boolean] Wait for actively executing jobs to finish
|
101
88
|
# @return [void]
|
102
89
|
def shutdown(wait: true)
|
103
|
-
|
90
|
+
return unless @pool&.running?
|
104
91
|
|
105
92
|
instrument("scheduler_shutdown_start", { wait: wait })
|
106
93
|
instrument("scheduler_shutdown", { wait: wait }) do
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
# TODO: Should be killed if wait is not true
|
111
|
-
end
|
112
|
-
|
113
|
-
if @pool&.running?
|
114
|
-
@pool.shutdown
|
115
|
-
@pool.wait_for_termination if wait
|
116
|
-
# TODO: Should be killed if wait is not true
|
117
|
-
end
|
94
|
+
@pool.shutdown
|
95
|
+
@pool.wait_for_termination if wait
|
96
|
+
# TODO: Should be killed if wait is not true
|
118
97
|
end
|
119
98
|
end
|
120
99
|
|
121
100
|
# Tests whether the scheduler is shutdown.
|
122
101
|
# @return [true, false, nil]
|
123
102
|
def shutdown?
|
124
|
-
|
103
|
+
!@pool&.running?
|
125
104
|
end
|
126
105
|
|
127
106
|
# Restart the Scheduler.
|
@@ -131,8 +110,7 @@ module GoodJob # :nodoc:
|
|
131
110
|
def restart(wait: true)
|
132
111
|
instrument("scheduler_restart_pools") do
|
133
112
|
shutdown(wait: wait) unless shutdown?
|
134
|
-
|
135
|
-
@_shutdown = false
|
113
|
+
create_pool
|
136
114
|
end
|
137
115
|
end
|
138
116
|
|
@@ -157,14 +135,6 @@ module GoodJob # :nodoc:
|
|
157
135
|
true
|
158
136
|
end
|
159
137
|
|
160
|
-
# Invoked on completion of TimerTask task.
|
161
|
-
# @!visibility private
|
162
|
-
# @return [void]
|
163
|
-
def timer_observer(time, executed_task, thread_error)
|
164
|
-
GoodJob.on_thread_error.call(thread_error) if thread_error && GoodJob.on_thread_error.respond_to?(:call)
|
165
|
-
instrument("finished_timer_task", { result: executed_task, error: thread_error, time: time })
|
166
|
-
end
|
167
|
-
|
168
138
|
# Invoked on completion of ThreadPoolExecutor task
|
169
139
|
# @!visibility private
|
170
140
|
# @return [void]
|
@@ -176,14 +146,9 @@ module GoodJob # :nodoc:
|
|
176
146
|
|
177
147
|
private
|
178
148
|
|
179
|
-
def
|
180
|
-
instrument("
|
149
|
+
def create_pool
|
150
|
+
instrument("scheduler_create_pool", { performer_name: @performer.name, max_threads: @pool_options[:max_threads] }) do
|
181
151
|
@pool = ThreadPoolExecutor.new(@pool_options)
|
182
|
-
next unless @timer_options[:execution_interval].positive?
|
183
|
-
|
184
|
-
@timer = Concurrent::TimerTask.new(@timer_options) { create_thread }
|
185
|
-
@timer.add_observer(self, :timer_observer)
|
186
|
-
@timer.execute
|
187
152
|
end
|
188
153
|
end
|
189
154
|
|
@@ -196,20 +161,20 @@ module GoodJob # :nodoc:
|
|
196
161
|
|
197
162
|
ActiveSupport::Notifications.instrument("#{name}.good_job", payload, &block)
|
198
163
|
end
|
199
|
-
end
|
200
164
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
165
|
+
# Custom sub-class of +Concurrent::ThreadPoolExecutor+ to add additional worker status.
|
166
|
+
# @private
|
167
|
+
class ThreadPoolExecutor < Concurrent::ThreadPoolExecutor
|
168
|
+
# Number of inactive threads available to execute tasks.
|
169
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/issues/684#issuecomment-427594437
|
170
|
+
# @return [Integer]
|
171
|
+
def ready_worker_count
|
172
|
+
synchronize do
|
173
|
+
workers_still_to_be_created = @max_length - @pool.length
|
174
|
+
workers_created_but_waiting = @ready.length
|
175
|
+
|
176
|
+
workers_still_to_be_created + workers_created_but_waiting
|
177
|
+
end
|
213
178
|
end
|
214
179
|
end
|
215
180
|
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: 1.
|
4
|
+
version: 1.3.4
|
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-
|
11
|
+
date: 2020-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -290,6 +290,20 @@ dependencies:
|
|
290
290
|
- - ">="
|
291
291
|
- !ruby/object:Gem::Version
|
292
292
|
version: '0'
|
293
|
+
- !ruby/object:Gem::Dependency
|
294
|
+
name: rails
|
295
|
+
requirement: !ruby/object:Gem::Requirement
|
296
|
+
requirements:
|
297
|
+
- - ">="
|
298
|
+
- !ruby/object:Gem::Version
|
299
|
+
version: '0'
|
300
|
+
type: :development
|
301
|
+
prerelease: false
|
302
|
+
version_requirements: !ruby/object:Gem::Requirement
|
303
|
+
requirements:
|
304
|
+
- - ">="
|
305
|
+
- !ruby/object:Gem::Version
|
306
|
+
version: '0'
|
293
307
|
- !ruby/object:Gem::Dependency
|
294
308
|
name: rbtrace
|
295
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -475,6 +489,7 @@ files:
|
|
475
489
|
- lib/good_job/multi_scheduler.rb
|
476
490
|
- lib/good_job/notifier.rb
|
477
491
|
- lib/good_job/performer.rb
|
492
|
+
- lib/good_job/poller.rb
|
478
493
|
- lib/good_job/railtie.rb
|
479
494
|
- lib/good_job/scheduler.rb
|
480
495
|
- lib/good_job/version.rb
|
@@ -510,7 +525,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
510
525
|
- !ruby/object:Gem::Version
|
511
526
|
version: '0'
|
512
527
|
requirements: []
|
513
|
-
rubygems_version: 3.
|
528
|
+
rubygems_version: 3.1.4
|
514
529
|
signing_key:
|
515
530
|
specification_version: 4
|
516
531
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|