good_job 1.3.0 → 1.3.5
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 +72 -2
- data/engine/app/{views/assets/_style.css.erb → assets/style.css} +0 -0
- data/engine/app/{views/vendor/bootstrap/_bootstrap-native.js.erb → assets/vendor/bootstrap/bootstrap-native.js} +0 -0
- data/engine/app/{views/vendor/bootstrap/_bootstrap.css.erb → assets/vendor/bootstrap/bootstrap.css} +0 -0
- data/engine/app/{views/vendor/chartist/_chartist.css.erb → assets/vendor/chartist/chartist.css} +0 -0
- data/engine/app/{views/vendor/chartist/_chartist.js.erb → assets/vendor/chartist/chartist.js} +0 -0
- 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 +8 -8
- data/engine/app/views/shared/_jobs_table.erb +3 -3
- data/lib/good_job/adapter.rb +3 -0
- data/lib/good_job/cli.rb +4 -1
- data/lib/good_job/lockable.rb +25 -11
- 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 +12 -81
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46b04fbfe44919834c406101ee18f2b53f5cc05e076525f7243e346fa966ca94
|
4
|
+
data.tar.gz: 798738a1f2b0c7f21457921c2c640cdc78151a09262dfa7d8a383e8778282b9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 841c85e7b4d9fa28d1fcdbb7c6a48771438f24ae33a264a80afdff1c9730a3438b98aa5d82f54bd1d4c979b2dab64997fbf6d64036bc96fdac6c851f70e0f68f
|
7
|
+
data.tar.gz: 9c2d11c56e3db986aa5bbecc85a317396a19ba1c838b97d32cd16f21b9cd29b6c154be817879d8c8a8925da645d298442c0ebc39aba3d5bba707ed01558b55c5
|
data/CHANGELOG.md
CHANGED
@@ -1,12 +1,82 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.3.5](https://github.com/bensheldon/good_job/tree/v1.3.5) (2020-12-17)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.4...v1.3.5)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- not running jobs [\#168](https://github.com/bensheldon/good_job/issues/168)
|
10
|
+
- how to run good\_job on a separate machine [\#162](https://github.com/bensheldon/good_job/issues/162)
|
11
|
+
|
12
|
+
**Merged pull requests:**
|
13
|
+
|
14
|
+
- Ensure advisory lock CTE is MATERIALIZED on Postgres v12+ [\#179](https://github.com/bensheldon/good_job/pull/179) ([bensheldon](https://github.com/bensheldon))
|
15
|
+
- Ensure that deleted jobs are unlocked [\#178](https://github.com/bensheldon/good_job/pull/178) ([bensheldon](https://github.com/bensheldon))
|
16
|
+
- Add Appraisal for Rails 6.1-rc2 [\#175](https://github.com/bensheldon/good_job/pull/175) ([bensheldon](https://github.com/bensheldon))
|
17
|
+
|
18
|
+
## [v1.3.4](https://github.com/bensheldon/good_job/tree/v1.3.4) (2020-12-02)
|
19
|
+
|
20
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.3...v1.3.4)
|
21
|
+
|
22
|
+
**Merged pull requests:**
|
23
|
+
|
24
|
+
- Fix job ordering for Rails 6.1 [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
|
25
|
+
|
26
|
+
## [v1.3.3](https://github.com/bensheldon/good_job/tree/v1.3.3) (2020-12-01)
|
27
|
+
|
28
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.2...v1.3.3)
|
29
|
+
|
30
|
+
**Merged pull requests:**
|
31
|
+
|
32
|
+
- UI: Admin UI with filters and space efficient layout [\#173](https://github.com/bensheldon/good_job/pull/173) ([zealot128](https://github.com/zealot128))
|
33
|
+
|
34
|
+
## [v1.3.2](https://github.com/bensheldon/good_job/tree/v1.3.2) (2020-11-12)
|
35
|
+
|
36
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.1...v1.3.2)
|
37
|
+
|
38
|
+
**Fixed bugs:**
|
39
|
+
|
40
|
+
- \(bug\) MultiScheduler polling bug [\#171](https://github.com/bensheldon/good_job/issues/171)
|
41
|
+
- MultiScheduler should delegate to all schedulers when state is nil [\#172](https://github.com/bensheldon/good_job/pull/172) ([bensheldon](https://github.com/bensheldon))
|
42
|
+
|
43
|
+
## [v1.3.1](https://github.com/bensheldon/good_job/tree/v1.3.1) (2020-11-01)
|
44
|
+
|
45
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.0...v1.3.1)
|
46
|
+
|
47
|
+
**Implemented enhancements:**
|
48
|
+
|
49
|
+
- Extract polling from scheduler into Polling object [\#128](https://github.com/bensheldon/good_job/issues/128)
|
50
|
+
- Format serialized params to ease reading [\#170](https://github.com/bensheldon/good_job/pull/170) ([morgoth](https://github.com/morgoth))
|
51
|
+
|
52
|
+
**Fixed bugs:**
|
53
|
+
|
54
|
+
- Don't disconnect a nil activerecord connection [\#161](https://github.com/bensheldon/good_job/pull/161) ([bensheldon](https://github.com/bensheldon))
|
55
|
+
|
56
|
+
**Closed issues:**
|
57
|
+
|
58
|
+
- Propose addition of GoodJob to queue-shootout benchmarks [\#40](https://github.com/bensheldon/good_job/issues/40)
|
59
|
+
|
60
|
+
**Merged pull requests:**
|
61
|
+
|
62
|
+
- Ensure Rails is a development dependency [\#169](https://github.com/bensheldon/good_job/pull/169) ([bensheldon](https://github.com/bensheldon))
|
63
|
+
- 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))
|
64
|
+
- Cache ruby version explicitly in Github Action [\#165](https://github.com/bensheldon/good_job/pull/165) ([bensheldon](https://github.com/bensheldon))
|
65
|
+
- Update development dependencies, rubocop [\#164](https://github.com/bensheldon/good_job/pull/164) ([bensheldon](https://github.com/bensheldon))
|
66
|
+
- Fix intended constant hierarchy of GoodJob::Scheduler::ThreadPoolExecutor [\#158](https://github.com/bensheldon/good_job/pull/158) ([bensheldon](https://github.com/bensheldon))
|
67
|
+
- Add bin/test\_app executable for Rails debugging [\#157](https://github.com/bensheldon/good_job/pull/157) ([bensheldon](https://github.com/bensheldon))
|
68
|
+
- Extract Scheduler polling behavior to its own object [\#152](https://github.com/bensheldon/good_job/pull/152) ([bensheldon](https://github.com/bensheldon))
|
69
|
+
|
3
70
|
## [v1.3.0](https://github.com/bensheldon/good_job/tree/v1.3.0) (2020-10-03)
|
4
71
|
|
5
72
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.2.6...v1.3.0)
|
6
73
|
|
7
|
-
**
|
74
|
+
**Implemented enhancements:**
|
8
75
|
|
9
76
|
- Lengthen default poll interval from 1 to 5 seconds [\#156](https://github.com/bensheldon/good_job/pull/156) ([bensheldon](https://github.com/bensheldon))
|
77
|
+
|
78
|
+
**Merged pull requests:**
|
79
|
+
|
10
80
|
- 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))
|
11
81
|
|
12
82
|
## [v1.2.6](https://github.com/bensheldon/good_job/tree/v1.2.6) (2020-09-29)
|
@@ -358,7 +428,6 @@
|
|
358
428
|
**Merged pull requests:**
|
359
429
|
|
360
430
|
- Improve ActiveRecord usage for advisory locking [\#24](https://github.com/bensheldon/good_job/pull/24) ([bensheldon](https://github.com/bensheldon))
|
361
|
-
- Remove support for Rails 5.1 [\#23](https://github.com/bensheldon/good_job/pull/23) ([bensheldon](https://github.com/bensheldon))
|
362
431
|
|
363
432
|
## [v0.3.0](https://github.com/bensheldon/good_job/tree/v0.3.0) (2020-03-22)
|
364
433
|
|
@@ -377,6 +446,7 @@
|
|
377
446
|
|
378
447
|
**Merged pull requests:**
|
379
448
|
|
449
|
+
- Remove support for Rails 5.1 [\#23](https://github.com/bensheldon/good_job/pull/23) ([bensheldon](https://github.com/bensheldon))
|
380
450
|
- Gracefully shutdown Scheduler when executable receives TERM or INT [\#17](https://github.com/bensheldon/good_job/pull/17) ([bensheldon](https://github.com/bensheldon))
|
381
451
|
- Update Appraisals [\#16](https://github.com/bensheldon/good_job/pull/16) ([bensheldon](https://github.com/bensheldon))
|
382
452
|
|
File without changes
|
File without changes
|
data/engine/app/{views/vendor/bootstrap/_bootstrap.css.erb → assets/vendor/bootstrap/bootstrap.css}
RENAMED
File without changes
|
data/engine/app/{views/vendor/chartist/_chartist.css.erb → assets/vendor/chartist/chartist.css}
RENAMED
File without changes
|
data/engine/app/{views/vendor/chartist/_chartist.js.erb → assets/vendor/chartist/chartist.js}
RENAMED
File without changes
|
@@ -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>
|
@@ -1,24 +1,24 @@
|
|
1
1
|
<!DOCTYPE html>
|
2
2
|
<html>
|
3
3
|
<head>
|
4
|
-
<title>Good
|
4
|
+
<title>Good Job Dashboard</title>
|
5
5
|
<%= csrf_meta_tags %>
|
6
6
|
<%= csp_meta_tag %>
|
7
7
|
|
8
8
|
<style>
|
9
|
-
<%= render "vendor
|
10
|
-
<%= render "vendor
|
11
|
-
<%= render "assets
|
9
|
+
<%= render file: GoodJob::Engine.root.join("app", "assets", "vendor", "bootstrap", "bootstrap.css") %>
|
10
|
+
<%= render file: GoodJob::Engine.root.join("app", "assets", "vendor", "chartist", "chartist.css") %>
|
11
|
+
<%= render file: GoodJob::Engine.root.join("app", "assets", "style.css") %>
|
12
12
|
</style>
|
13
13
|
|
14
14
|
<script>
|
15
|
-
<%= render "vendor
|
16
|
-
<%= render "vendor
|
15
|
+
<%= render file: GoodJob::Engine.root.join("app", "assets", "vendor", "bootstrap", "bootstrap-native.js") %>
|
16
|
+
<%= render file: GoodJob::Engine.root.join("app", "assets", "vendor", "chartist", "chartist.js") %>
|
17
17
|
</script>
|
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/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
@@ -45,11 +45,13 @@ module GoodJob
|
|
45
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
|
|
data/lib/good_job/lockable.rb
CHANGED
@@ -32,11 +32,18 @@ module GoodJob
|
|
32
32
|
original_query = self
|
33
33
|
|
34
34
|
cte_table = Arel::Table.new(:rows)
|
35
|
-
|
35
|
+
cte_query = original_query.select(primary_key).except(:limit)
|
36
|
+
cte_type = if supports_cte_materialization_specifiers?
|
37
|
+
'MATERIALIZED'
|
38
|
+
else
|
39
|
+
''
|
40
|
+
end
|
41
|
+
|
42
|
+
composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::SqlLiteral.new([cte_type, "(", cte_query.to_sql, ")"].join(' ')))
|
36
43
|
|
37
44
|
query = cte_table.project(cte_table[:id])
|
38
|
-
|
39
|
-
|
45
|
+
.with(composed_cte)
|
46
|
+
.where(Arel.sql(sanitize_sql_for_conditions(["pg_try_advisory_lock(('x' || substr(md5(:table_name || #{connection.quote_table_name(cte_table.name)}.#{quoted_primary_key}::text), 1, 16))::bit(64)::bigint)", { table_name: table_name }])))
|
40
47
|
|
41
48
|
limit = original_query.arel.ast.limit
|
42
49
|
query.limit = limit.value if limit.present?
|
@@ -56,7 +63,7 @@ module GoodJob
|
|
56
63
|
# @example Get the records that have a session awaiting a lock:
|
57
64
|
# MyLockableRecord.joins_advisory_locks.where("pg_locks.granted = ?", false)
|
58
65
|
scope :joins_advisory_locks, (lambda do
|
59
|
-
join_sql = <<~SQL
|
66
|
+
join_sql = <<~SQL.squish
|
60
67
|
LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
|
61
68
|
AND pg_locks.objsubid = 1
|
62
69
|
AND pg_locks.classid = ('x' || substr(md5(:table_name || #{quoted_table_name}.#{quoted_primary_key}::text), 1, 16))::bit(32)::int
|
@@ -132,6 +139,12 @@ module GoodJob
|
|
132
139
|
records.each(&:advisory_unlock)
|
133
140
|
end
|
134
141
|
end
|
142
|
+
|
143
|
+
def supports_cte_materialization_specifiers?
|
144
|
+
return @supports_cte_materialization_specifiers if defined?(@supports_cte_materialization_specifiers)
|
145
|
+
|
146
|
+
@supports_cte_materialization_specifiers = ActiveRecord::Base.connection.postgresql_version >= 120000
|
147
|
+
end
|
135
148
|
end
|
136
149
|
|
137
150
|
# Acquires an advisory lock on this record if it is not already locked by
|
@@ -140,10 +153,10 @@ module GoodJob
|
|
140
153
|
# all remaining locks).
|
141
154
|
# @return [Boolean] whether the lock was acquired.
|
142
155
|
def advisory_lock
|
143
|
-
where_sql = <<~SQL
|
156
|
+
where_sql = <<~SQL.squish
|
144
157
|
pg_try_advisory_lock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
145
158
|
SQL
|
146
|
-
self.class.unscoped.
|
159
|
+
self.class.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }])
|
147
160
|
end
|
148
161
|
|
149
162
|
# Releases an advisory lock on this record if it is locked by this database
|
@@ -151,10 +164,11 @@ module GoodJob
|
|
151
164
|
# {#advisory_unlock} and {#advisory_lock} the same number of times.
|
152
165
|
# @return [Boolean] whether the lock was released.
|
153
166
|
def advisory_unlock
|
154
|
-
|
155
|
-
|
167
|
+
query = <<~SQL.squish
|
168
|
+
SELECT 1 AS one
|
169
|
+
WHERE pg_advisory_unlock(('x'||substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
|
156
170
|
SQL
|
157
|
-
self.class.
|
171
|
+
self.class.connection.execute(sanitize_sql_for_conditions([query, { table_name: self.class.table_name, id: send(self.class.primary_key) }])).ntuples.positive?
|
158
172
|
end
|
159
173
|
|
160
174
|
# Acquires an advisory lock on this record or raises
|
@@ -191,13 +205,13 @@ module GoodJob
|
|
191
205
|
# Tests whether this record has an advisory lock on it.
|
192
206
|
# @return [Boolean]
|
193
207
|
def advisory_locked?
|
194
|
-
self.class.unscoped.advisory_locked.
|
208
|
+
self.class.unscoped.advisory_locked.exists?(id: send(self.class.primary_key))
|
195
209
|
end
|
196
210
|
|
197
211
|
# Tests whether this record is locked by the current database session.
|
198
212
|
# @return [Boolean]
|
199
213
|
def owns_advisory_lock?
|
200
|
-
self.class.unscoped.owns_advisory_locked.
|
214
|
+
self.class.unscoped.owns_advisory_locked.exists?(id: send(self.class.primary_key))
|
201
215
|
end
|
202
216
|
|
203
217
|
# 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.3.
|
4
|
+
version: 1.3.5
|
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-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -164,20 +164,6 @@ dependencies:
|
|
164
164
|
- - ">="
|
165
165
|
- !ruby/object:Gem::Version
|
166
166
|
version: '0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: erb_lint
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - ">="
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - ">="
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0'
|
181
167
|
- !ruby/object:Gem::Dependency
|
182
168
|
name: foreman
|
183
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -248,20 +234,6 @@ dependencies:
|
|
248
234
|
- - ">="
|
249
235
|
- !ruby/object:Gem::Version
|
250
236
|
version: '0'
|
251
|
-
- !ruby/object:Gem::Dependency
|
252
|
-
name: mdl
|
253
|
-
requirement: !ruby/object:Gem::Requirement
|
254
|
-
requirements:
|
255
|
-
- - ">="
|
256
|
-
- !ruby/object:Gem::Version
|
257
|
-
version: '0'
|
258
|
-
type: :development
|
259
|
-
prerelease: false
|
260
|
-
version_requirements: !ruby/object:Gem::Requirement
|
261
|
-
requirements:
|
262
|
-
- - ">="
|
263
|
-
- !ruby/object:Gem::Version
|
264
|
-
version: '0'
|
265
237
|
- !ruby/object:Gem::Dependency
|
266
238
|
name: pry-rails
|
267
239
|
requirement: !ruby/object:Gem::Requirement
|
@@ -291,7 +263,7 @@ dependencies:
|
|
291
263
|
- !ruby/object:Gem::Version
|
292
264
|
version: '0'
|
293
265
|
- !ruby/object:Gem::Dependency
|
294
|
-
name:
|
266
|
+
name: rails
|
295
267
|
requirement: !ruby/object:Gem::Requirement
|
296
268
|
requirements:
|
297
269
|
- - ">="
|
@@ -305,49 +277,7 @@ dependencies:
|
|
305
277
|
- !ruby/object:Gem::Version
|
306
278
|
version: '0'
|
307
279
|
- !ruby/object:Gem::Dependency
|
308
|
-
name:
|
309
|
-
requirement: !ruby/object:Gem::Requirement
|
310
|
-
requirements:
|
311
|
-
- - ">="
|
312
|
-
- !ruby/object:Gem::Version
|
313
|
-
version: '0'
|
314
|
-
type: :development
|
315
|
-
prerelease: false
|
316
|
-
version_requirements: !ruby/object:Gem::Requirement
|
317
|
-
requirements:
|
318
|
-
- - ">="
|
319
|
-
- !ruby/object:Gem::Version
|
320
|
-
version: '0'
|
321
|
-
- !ruby/object:Gem::Dependency
|
322
|
-
name: rubocop
|
323
|
-
requirement: !ruby/object:Gem::Requirement
|
324
|
-
requirements:
|
325
|
-
- - ">="
|
326
|
-
- !ruby/object:Gem::Version
|
327
|
-
version: '0'
|
328
|
-
type: :development
|
329
|
-
prerelease: false
|
330
|
-
version_requirements: !ruby/object:Gem::Requirement
|
331
|
-
requirements:
|
332
|
-
- - ">="
|
333
|
-
- !ruby/object:Gem::Version
|
334
|
-
version: '0'
|
335
|
-
- !ruby/object:Gem::Dependency
|
336
|
-
name: rubocop-performance
|
337
|
-
requirement: !ruby/object:Gem::Requirement
|
338
|
-
requirements:
|
339
|
-
- - ">="
|
340
|
-
- !ruby/object:Gem::Version
|
341
|
-
version: '0'
|
342
|
-
type: :development
|
343
|
-
prerelease: false
|
344
|
-
version_requirements: !ruby/object:Gem::Requirement
|
345
|
-
requirements:
|
346
|
-
- - ">="
|
347
|
-
- !ruby/object:Gem::Version
|
348
|
-
version: '0'
|
349
|
-
- !ruby/object:Gem::Dependency
|
350
|
-
name: rubocop-rails
|
280
|
+
name: rbtrace
|
351
281
|
requirement: !ruby/object:Gem::Requirement
|
352
282
|
requirements:
|
353
283
|
- - ">="
|
@@ -361,7 +291,7 @@ dependencies:
|
|
361
291
|
- !ruby/object:Gem::Version
|
362
292
|
version: '0'
|
363
293
|
- !ruby/object:Gem::Dependency
|
364
|
-
name:
|
294
|
+
name: rspec-rails
|
365
295
|
requirement: !ruby/object:Gem::Requirement
|
366
296
|
requirements:
|
367
297
|
- - ">="
|
@@ -444,20 +374,20 @@ files:
|
|
444
374
|
- CHANGELOG.md
|
445
375
|
- LICENSE.txt
|
446
376
|
- README.md
|
377
|
+
- engine/app/assets/style.css
|
378
|
+
- engine/app/assets/vendor/bootstrap/bootstrap-native.js
|
379
|
+
- engine/app/assets/vendor/bootstrap/bootstrap.css
|
380
|
+
- engine/app/assets/vendor/chartist/chartist.css
|
381
|
+
- engine/app/assets/vendor/chartist/chartist.js
|
447
382
|
- engine/app/controllers/good_job/active_jobs_controller.rb
|
448
383
|
- engine/app/controllers/good_job/base_controller.rb
|
449
384
|
- engine/app/controllers/good_job/dashboards_controller.rb
|
450
385
|
- engine/app/helpers/good_job/application_helper.rb
|
451
|
-
- engine/app/views/assets/_style.css.erb
|
452
386
|
- engine/app/views/good_job/active_jobs/show.html.erb
|
453
387
|
- engine/app/views/good_job/dashboards/index.html.erb
|
454
388
|
- engine/app/views/layouts/good_job/base.html.erb
|
455
389
|
- engine/app/views/shared/_chart.erb
|
456
390
|
- engine/app/views/shared/_jobs_table.erb
|
457
|
-
- engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb
|
458
|
-
- engine/app/views/vendor/bootstrap/_bootstrap.css.erb
|
459
|
-
- engine/app/views/vendor/chartist/_chartist.css.erb
|
460
|
-
- engine/app/views/vendor/chartist/_chartist.js.erb
|
461
391
|
- engine/config/routes.rb
|
462
392
|
- engine/lib/good_job/engine.rb
|
463
393
|
- exe/good_job
|
@@ -475,6 +405,7 @@ files:
|
|
475
405
|
- lib/good_job/multi_scheduler.rb
|
476
406
|
- lib/good_job/notifier.rb
|
477
407
|
- lib/good_job/performer.rb
|
408
|
+
- lib/good_job/poller.rb
|
478
409
|
- lib/good_job/railtie.rb
|
479
410
|
- lib/good_job/scheduler.rb
|
480
411
|
- lib/good_job/version.rb
|
@@ -510,7 +441,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
510
441
|
- !ruby/object:Gem::Version
|
511
442
|
version: '0'
|
512
443
|
requirements: []
|
513
|
-
rubygems_version: 3.
|
444
|
+
rubygems_version: 3.1.4
|
514
445
|
signing_key:
|
515
446
|
specification_version: 4
|
516
447
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|