good_job 1.3.2 → 1.4.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 +70 -1
- data/README.md +7 -0
- 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 +51 -2
- data/engine/app/views/good_job/dashboards/index.html.erb +39 -4
- data/engine/app/views/layouts/good_job/base.html.erb +8 -8
- data/engine/app/views/shared/_chart.erb +1 -1
- data/engine/app/views/shared/_jobs_table.erb +27 -25
- data/lib/good_job/configuration.rb +1 -3
- data/lib/good_job/job.rb +7 -1
- data/lib/good_job/lockable.rb +56 -11
- data/lib/good_job/log_subscriber.rb +7 -7
- data/lib/good_job/notifier.rb +25 -14
- data/lib/good_job/scheduler.rb +8 -5
- data/lib/good_job/version.rb +1 -1
- metadata +8 -134
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a970811c9e009e460f65ed53b23913ae530e2a4018811ed63aa382ac673ad8a
|
4
|
+
data.tar.gz: 5678206af710fdbc275486529451356ae7ab8b2f52c64e8743db4789c806319d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a1cc256e46120045b1c5f9583c9c9f6ee948a60b8e3a0f4a0dd6ee74be411dbe3d8303c6b8c0c536804f563b8819174a3840814a66f4e4110ba25244a25cffb
|
7
|
+
data.tar.gz: 11224d38b15c50108e14e529f645de8e9991aeb265414fea4513aaa253da6c13fcc9b981132516497db53f185430bb1b775ee1f5964cd8d9ece528a9a5353f70
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,74 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.4.0](https://github.com/bensheldon/good_job/tree/v1.4.0) (2020-12-31)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.6...v1.4.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add JRuby support [\#167](https://github.com/bensheldon/good_job/pull/167) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
11
|
+
**Closed issues:**
|
12
|
+
|
13
|
+
- JRuby Support [\#160](https://github.com/bensheldon/good_job/issues/160)
|
14
|
+
|
15
|
+
## [v1.3.6](https://github.com/bensheldon/good_job/tree/v1.3.6) (2020-12-30)
|
16
|
+
|
17
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.5...v1.3.6)
|
18
|
+
|
19
|
+
**Implemented enhancements:**
|
20
|
+
|
21
|
+
- Call GoodJob.on\_thread\_error when Notifier thread raises exception [\#185](https://github.com/bensheldon/good_job/pull/185) ([bensheldon](https://github.com/bensheldon))
|
22
|
+
- Improve dashboard UI, fix button state, add unfiltering [\#181](https://github.com/bensheldon/good_job/pull/181) ([bensheldon](https://github.com/bensheldon))
|
23
|
+
|
24
|
+
**Fixed bugs:**
|
25
|
+
|
26
|
+
- Replace ActiveRecord execute usage and avoid potential memory leakage [\#187](https://github.com/bensheldon/good_job/issues/187)
|
27
|
+
- Does good\_job hold on to advisory locks for finished jobs? [\#177](https://github.com/bensheldon/good_job/issues/177)
|
28
|
+
|
29
|
+
**Merged pull requests:**
|
30
|
+
|
31
|
+
- Run tests with Rails default configuration to enable Zeitwerk [\#190](https://github.com/bensheldon/good_job/pull/190) ([bensheldon](https://github.com/bensheldon))
|
32
|
+
- Update all Lockable queries to use exec\_query instead of execute; clear async\_exec results [\#189](https://github.com/bensheldon/good_job/pull/189) ([bensheldon](https://github.com/bensheldon))
|
33
|
+
- Have Lockable\#advisory\_locked? directly query pg\_locks table [\#188](https://github.com/bensheldon/good_job/pull/188) ([bensheldon](https://github.com/bensheldon))
|
34
|
+
- Update development gems, including Rails v6.1 and Rails HEAD [\#186](https://github.com/bensheldon/good_job/pull/186) ([bensheldon](https://github.com/bensheldon))
|
35
|
+
- Update Appraisals for Rails 6.1 [\#183](https://github.com/bensheldon/good_job/pull/183) ([bensheldon](https://github.com/bensheldon))
|
36
|
+
- Add Ruby 3 to CI test matrix [\#182](https://github.com/bensheldon/good_job/pull/182) ([bensheldon](https://github.com/bensheldon))
|
37
|
+
|
38
|
+
## [v1.3.5](https://github.com/bensheldon/good_job/tree/v1.3.5) (2020-12-17)
|
39
|
+
|
40
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.4...v1.3.5)
|
41
|
+
|
42
|
+
**Fixed bugs:**
|
43
|
+
|
44
|
+
- Ensure advisory lock CTE is MATERIALIZED on Postgres v12+ [\#179](https://github.com/bensheldon/good_job/pull/179) ([bensheldon](https://github.com/bensheldon))
|
45
|
+
- Ensure that deleted jobs are unlocked [\#178](https://github.com/bensheldon/good_job/pull/178) ([bensheldon](https://github.com/bensheldon))
|
46
|
+
|
47
|
+
**Closed issues:**
|
48
|
+
|
49
|
+
- not running jobs [\#168](https://github.com/bensheldon/good_job/issues/168)
|
50
|
+
- how to run good\_job on a separate machine [\#162](https://github.com/bensheldon/good_job/issues/162)
|
51
|
+
|
52
|
+
**Merged pull requests:**
|
53
|
+
|
54
|
+
- Add Appraisal for Rails 6.1-rc2 [\#175](https://github.com/bensheldon/good_job/pull/175) ([bensheldon](https://github.com/bensheldon))
|
55
|
+
|
56
|
+
## [v1.3.4](https://github.com/bensheldon/good_job/tree/v1.3.4) (2020-12-02)
|
57
|
+
|
58
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.3...v1.3.4)
|
59
|
+
|
60
|
+
**Fixed bugs:**
|
61
|
+
|
62
|
+
- Fix job ordering for Rails 6.1 [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
|
63
|
+
|
64
|
+
## [v1.3.3](https://github.com/bensheldon/good_job/tree/v1.3.3) (2020-12-01)
|
65
|
+
|
66
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.2...v1.3.3)
|
67
|
+
|
68
|
+
**Implemented enhancements:**
|
69
|
+
|
70
|
+
- UI: Admin UI with filters and space efficient layout [\#173](https://github.com/bensheldon/good_job/pull/173) ([zealot128](https://github.com/zealot128))
|
71
|
+
|
3
72
|
## [v1.3.2](https://github.com/bensheldon/good_job/tree/v1.3.2) (2020-11-12)
|
4
73
|
|
5
74
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.1...v1.3.2)
|
@@ -32,7 +101,6 @@
|
|
32
101
|
- 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))
|
33
102
|
- Cache ruby version explicitly in Github Action [\#165](https://github.com/bensheldon/good_job/pull/165) ([bensheldon](https://github.com/bensheldon))
|
34
103
|
- Update development dependencies, rubocop [\#164](https://github.com/bensheldon/good_job/pull/164) ([bensheldon](https://github.com/bensheldon))
|
35
|
-
- Fix intended constant hierarchy of GoodJob::Scheduler::ThreadPoolExecutor [\#158](https://github.com/bensheldon/good_job/pull/158) ([bensheldon](https://github.com/bensheldon))
|
36
104
|
- Add bin/test\_app executable for Rails debugging [\#157](https://github.com/bensheldon/good_job/pull/157) ([bensheldon](https://github.com/bensheldon))
|
37
105
|
- Extract Scheduler polling behavior to its own object [\#152](https://github.com/bensheldon/good_job/pull/152) ([bensheldon](https://github.com/bensheldon))
|
38
106
|
|
@@ -67,6 +135,7 @@
|
|
67
135
|
|
68
136
|
**Merged pull requests:**
|
69
137
|
|
138
|
+
- Fix intended constant hierarchy of GoodJob::Scheduler::ThreadPoolExecutor [\#158](https://github.com/bensheldon/good_job/pull/158) ([bensheldon](https://github.com/bensheldon))
|
70
139
|
- Add info how to setup basic auth for engine [\#153](https://github.com/bensheldon/good_job/pull/153) ([morgoth](https://github.com/morgoth))
|
71
140
|
- Add documentation for Dashboard Rails::Engine [\#149](https://github.com/bensheldon/good_job/pull/149) ([bensheldon](https://github.com/bensheldon))
|
72
141
|
- Style cleanup to Job error handling [\#147](https://github.com/bensheldon/good_job/pull/147) ([bensheldon](https://github.com/bensheldon))
|
data/README.md
CHANGED
@@ -30,6 +30,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
30
30
|
## Table of contents
|
31
31
|
|
32
32
|
- [Set up](#set-up)
|
33
|
+
- [Compatibility](#compatibility)
|
33
34
|
- [Configuration](#configuration)
|
34
35
|
- [Command-line options](#command-line-options)
|
35
36
|
- [`good_job start`](#good_job-start)
|
@@ -123,6 +124,12 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
123
124
|
|
124
125
|
Additional configuration is likely necessary, see the reference below for async configuration.
|
125
126
|
|
127
|
+
## Compatibility
|
128
|
+
|
129
|
+
- **Ruby on Rails:** 5.2+
|
130
|
+
- **Ruby:** MRI 2.5+. JRuby 9.13+ (_JRuby's `activerecord-jdbcpostgresql-adapter` gem does not support Postgres LISTEN/NOTIFY)._
|
131
|
+
- **Postgres:** 9.6+
|
132
|
+
|
126
133
|
## Configuration
|
127
134
|
|
128
135
|
### Command-line options
|
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,8 +1,57 @@
|
|
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_params(override)
|
46
|
+
{
|
47
|
+
state: params[:state],
|
48
|
+
job_class: params[:job_class],
|
49
|
+
}.merge(override).delete_if { |_, v| v.nil? }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def index
|
54
|
+
@filter = JobFilter.new(params)
|
6
55
|
|
7
56
|
job_data = GoodJob::Job.connection.exec_query Arel.sql(<<~SQL.squish)
|
8
57
|
SELECT *
|
@@ -2,13 +2,48 @@
|
|
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
|
+
<% if params[:job_class] == name %>
|
12
|
+
<%= link_to(root_path(@filter.to_params(job_class: nil)), class: 'btn btn-sm btn-outline-secondary active', role: "button", "aria-pressed": true) do %>
|
13
|
+
<%= name %> (<%= count %>)
|
14
|
+
<% end %>
|
15
|
+
<% else %>
|
16
|
+
<%= link_to(root_path(@filter.to_params(job_class: name)), class: 'btn btn-sm btn-outline-secondary', role: "button") do %>
|
17
|
+
<%= name %> (<%= count %>)
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
</div>
|
22
|
+
<div>
|
23
|
+
<small>Filter by state</small>
|
24
|
+
<br>
|
25
|
+
<% @filter.states.each do |name, count| %>
|
26
|
+
<% if params[:state] == name %>
|
27
|
+
<%= link_to(root_path(@filter.to_params(state: nil)), class: 'btn btn-sm btn-outline-secondary active', role: "button", "aria-pressed": true) do %>
|
28
|
+
<%= name %> (<%= count %>)
|
29
|
+
<% end %>
|
30
|
+
<% else %>
|
31
|
+
<%= link_to(root_path(@filter.to_params(state: name)), class: 'btn btn-sm btn-outline-secondary', role: "button") do %>
|
32
|
+
<%= name %> (<%= count %>)
|
33
|
+
<% end %>
|
34
|
+
<% end %>
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
|
40
|
+
<% if @filter.jobs.present? %>
|
41
|
+
<%= render 'shared/jobs_table', jobs: @filter.jobs %>
|
7
42
|
|
8
|
-
<nav aria-label="Job pagination">
|
43
|
+
<nav aria-label="Job pagination" class="mt-3">
|
9
44
|
<ul class="pagination">
|
10
45
|
<li class="page-item">
|
11
|
-
<%= link_to({ after_scheduled_at: (@
|
46
|
+
<%= link_to({ after_scheduled_at: (@filter.last.scheduled_at || @filter.last.created_at), after_id: @filter.last.id }, class: "page-link") do %>
|
12
47
|
Next jobs <span aria-hidden="true">»</span>
|
13
48
|
<% end %>
|
14
49
|
</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
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
16
|
-
|
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,26 +1,28 @@
|
|
1
|
-
<div class="
|
2
|
-
<
|
3
|
-
<
|
4
|
-
<
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
<
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
1
|
+
<div class="card my-3">
|
2
|
+
<div class="table-responsive">
|
3
|
+
<table class="table card-table table-bordered table-hover table-sm mb-0">
|
4
|
+
<thead>
|
5
|
+
<th>GoodJob ID</th>
|
6
|
+
<th>ActiveJob ID</th>
|
7
|
+
<th>Job Class</th>
|
8
|
+
<th>Queue</th>
|
9
|
+
<th>Scheduled At</th>
|
10
|
+
<th>Error</th>
|
11
|
+
<th>ActiveJob Params</th>
|
12
|
+
</thead>
|
13
|
+
<tbody>
|
14
|
+
<% jobs.each do |job| %>
|
15
|
+
<tr id="<%= dom_id(job) %>">
|
16
|
+
<td><%= link_to job.id, active_job_path(job.serialized_params['job_id'], anchor: dom_id(job)) %></td>
|
17
|
+
<td><%= link_to job.serialized_params['job_id'], active_job_path(job.serialized_params['job_id']) %></td>
|
18
|
+
<td><%= job.serialized_params['job_class'] %></td>
|
19
|
+
<td><%= job.queue_name %></td>
|
20
|
+
<td><%= job.scheduled_at || job.created_at %></td>
|
21
|
+
<td><%= job.error %></td>
|
22
|
+
<td><pre><%= JSON.pretty_generate(job.serialized_params) %></pre></td>
|
23
|
+
</tr>
|
24
|
+
<% end %>
|
25
|
+
</tbody>
|
26
|
+
</table>
|
27
|
+
</div>
|
26
28
|
</div>
|
data/lib/good_job/job.rb
CHANGED
@@ -139,7 +139,7 @@ module GoodJob
|
|
139
139
|
unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock do |good_jobs|
|
140
140
|
good_job = good_jobs.first
|
141
141
|
# TODO: Determine why some records are fetched without an advisory lock at all
|
142
|
-
break unless good_job&.
|
142
|
+
break unless good_job&.executable?
|
143
143
|
|
144
144
|
result, error = good_job.perform
|
145
145
|
end
|
@@ -216,6 +216,12 @@ module GoodJob
|
|
216
216
|
[result, job_error]
|
217
217
|
end
|
218
218
|
|
219
|
+
# Tests whether this job is safe to be executed by this thread.
|
220
|
+
# @return [Boolean]
|
221
|
+
def executable?
|
222
|
+
self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
|
223
|
+
end
|
224
|
+
|
219
225
|
private
|
220
226
|
|
221
227
|
def execute
|
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?
|
@@ -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,12 @@ module GoodJob
|
|
140
153
|
# all remaining locks).
|
141
154
|
# @return [Boolean] whether the lock was acquired.
|
142
155
|
def advisory_lock
|
143
|
-
|
144
|
-
|
156
|
+
query = <<~SQL.squish
|
157
|
+
SELECT 1 AS one
|
158
|
+
WHERE pg_try_advisory_lock(('x'||substr(md5($1 || $2::text), 1, 16))::bit(64)::bigint)
|
145
159
|
SQL
|
146
|
-
|
160
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
161
|
+
ActiveRecord::Base.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Lock', binds).any?
|
147
162
|
end
|
148
163
|
|
149
164
|
# Releases an advisory lock on this record if it is locked by this database
|
@@ -151,10 +166,12 @@ module GoodJob
|
|
151
166
|
# {#advisory_unlock} and {#advisory_lock} the same number of times.
|
152
167
|
# @return [Boolean] whether the lock was released.
|
153
168
|
def advisory_unlock
|
154
|
-
|
155
|
-
|
169
|
+
query = <<~SQL.squish
|
170
|
+
SELECT 1 AS one
|
171
|
+
WHERE pg_advisory_unlock(('x'||substr(md5($1 || $2::text), 1, 16))::bit(64)::bigint)
|
156
172
|
SQL
|
157
|
-
|
173
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
174
|
+
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Unlock', binds).any?
|
158
175
|
end
|
159
176
|
|
160
177
|
# Acquires an advisory lock on this record or raises
|
@@ -191,13 +208,32 @@ module GoodJob
|
|
191
208
|
# Tests whether this record has an advisory lock on it.
|
192
209
|
# @return [Boolean]
|
193
210
|
def advisory_locked?
|
194
|
-
|
211
|
+
query = <<~SQL.squish
|
212
|
+
SELECT 1 AS one
|
213
|
+
FROM pg_locks
|
214
|
+
WHERE pg_locks.locktype = 'advisory'
|
215
|
+
AND pg_locks.objsubid = 1
|
216
|
+
AND pg_locks.classid = ('x' || substr(md5($1 || $2::text), 1, 16))::bit(32)::int
|
217
|
+
AND pg_locks.objid = (('x' || substr(md5($3 || $4::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
218
|
+
SQL
|
219
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)], [nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
220
|
+
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Advisory Locked?', binds).any?
|
195
221
|
end
|
196
222
|
|
197
223
|
# Tests whether this record is locked by the current database session.
|
198
224
|
# @return [Boolean]
|
199
225
|
def owns_advisory_lock?
|
200
|
-
|
226
|
+
query = <<~SQL.squish
|
227
|
+
SELECT 1 AS one
|
228
|
+
FROM pg_locks
|
229
|
+
WHERE pg_locks.locktype = 'advisory'
|
230
|
+
AND pg_locks.objsubid = 1
|
231
|
+
AND pg_locks.classid = ('x' || substr(md5($1 || $2::text), 1, 16))::bit(32)::int
|
232
|
+
AND pg_locks.objid = (('x' || substr(md5($3 || $4::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
233
|
+
AND pg_locks.pid = pg_backend_pid()
|
234
|
+
SQL
|
235
|
+
binds = [[nil, self.class.table_name], [nil, send(self.class.primary_key)], [nil, self.class.table_name], [nil, send(self.class.primary_key)]]
|
236
|
+
self.class.connection.exec_query(pg_or_jdbc_query(query), 'GoodJob::Lockable Owns Advisory Lock?', binds).any?
|
201
237
|
end
|
202
238
|
|
203
239
|
# Releases all advisory locks on the record that are held by the current
|
@@ -213,5 +249,14 @@ module GoodJob
|
|
213
249
|
# Made public in Rails 5.2
|
214
250
|
self.class.send(:sanitize_sql_for_conditions, *args)
|
215
251
|
end
|
252
|
+
|
253
|
+
def pg_or_jdbc_query(query)
|
254
|
+
if Concurrent.on_jruby?
|
255
|
+
# Replace $1 bind parameters with ?
|
256
|
+
query.gsub(/\$\d*/, '?')
|
257
|
+
else
|
258
|
+
query
|
259
|
+
end
|
260
|
+
end
|
216
261
|
end
|
217
262
|
end
|
@@ -218,13 +218,13 @@ module GoodJob
|
|
218
218
|
#
|
219
219
|
%w(info debug warn error fatal unknown).each do |level|
|
220
220
|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
|
221
|
-
def #{level}(progname = nil, tags: [], &block)
|
222
|
-
return unless logger
|
223
|
-
|
224
|
-
tag_logger(*tags) do
|
225
|
-
logger.#{level}(progname, &block)
|
226
|
-
end
|
227
|
-
end
|
221
|
+
def #{level}(progname = nil, tags: [], &block) # def info(progname = nil, tags: [], &block)
|
222
|
+
return unless logger # return unless logger
|
223
|
+
#
|
224
|
+
tag_logger(*tags) do # tag_logger(*tags) do
|
225
|
+
logger.#{level}(progname, &block) # logger.info(progname, &block)
|
226
|
+
end # end
|
227
|
+
end #
|
228
228
|
METHOD
|
229
229
|
end
|
230
230
|
end
|
data/lib/good_job/notifier.rb
CHANGED
@@ -9,6 +9,8 @@ module GoodJob # :nodoc:
|
|
9
9
|
# When a message is received, the notifier passes the message to each of its recipients.
|
10
10
|
#
|
11
11
|
class Notifier
|
12
|
+
AdapterCannotListenError = Class.new(StandardError)
|
13
|
+
|
12
14
|
# Default Postgres channel for LISTEN/NOTIFY
|
13
15
|
CHANNEL = 'good_job'.freeze
|
14
16
|
# Defaults for instance of Concurrent::ThreadPoolExecutor
|
@@ -90,6 +92,20 @@ module GoodJob # :nodoc:
|
|
90
92
|
!@pool.running?
|
91
93
|
end
|
92
94
|
|
95
|
+
# Invoked on completion of ThreadPoolExecutor task
|
96
|
+
# @!visibility private
|
97
|
+
# @return [void]
|
98
|
+
def listen_observer(_time, _result, thread_error)
|
99
|
+
return if thread_error.is_a? AdapterCannotListenError
|
100
|
+
|
101
|
+
if thread_error
|
102
|
+
GoodJob.on_thread_error.call(thread_error) if GoodJob.on_thread_error.respond_to?(:call)
|
103
|
+
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
|
104
|
+
end
|
105
|
+
|
106
|
+
listen unless shutdown?
|
107
|
+
end
|
108
|
+
|
93
109
|
private
|
94
110
|
|
95
111
|
def create_pool
|
@@ -100,7 +116,7 @@ module GoodJob # :nodoc:
|
|
100
116
|
future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
|
101
117
|
with_listen_connection do |conn|
|
102
118
|
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
103
|
-
conn.async_exec
|
119
|
+
conn.async_exec("LISTEN #{CHANNEL}").clear
|
104
120
|
end
|
105
121
|
|
106
122
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -120,14 +136,11 @@ module GoodJob # :nodoc:
|
|
120
136
|
listening.make_false
|
121
137
|
end
|
122
138
|
end
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
@listening.make_false
|
129
|
-
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
130
|
-
conn.async_exec "UNLISTEN *"
|
139
|
+
ensure
|
140
|
+
listening.make_false
|
141
|
+
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
142
|
+
conn.async_exec("UNLISTEN *").clear
|
143
|
+
end
|
131
144
|
end
|
132
145
|
end
|
133
146
|
|
@@ -135,16 +148,14 @@ module GoodJob # :nodoc:
|
|
135
148
|
future.execute
|
136
149
|
end
|
137
150
|
|
138
|
-
def listen_observer(_time, _result, _thread_error)
|
139
|
-
listen unless shutdown?
|
140
|
-
end
|
141
|
-
|
142
151
|
def with_listen_connection
|
143
152
|
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
|
144
153
|
ActiveRecord::Base.connection_pool.remove(conn)
|
145
154
|
end
|
146
155
|
pg_conn = ar_conn.raw_connection
|
147
|
-
|
156
|
+
raise AdapterCannotListenError unless pg_conn.respond_to? :wait_for_notify
|
157
|
+
|
158
|
+
pg_conn.async_exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}").clear
|
148
159
|
yield pg_conn
|
149
160
|
ensure
|
150
161
|
ar_conn&.disconnect!
|
data/lib/good_job/scheduler.rb
CHANGED
@@ -22,7 +22,7 @@ module GoodJob # :nodoc:
|
|
22
22
|
max_threads: Configuration::DEFAULT_MAX_THREADS,
|
23
23
|
auto_terminate: true,
|
24
24
|
idletime: 60,
|
25
|
-
max_queue:
|
25
|
+
max_queue: 0,
|
26
26
|
fallback_policy: :discard,
|
27
27
|
}.freeze
|
28
28
|
|
@@ -170,10 +170,13 @@ module GoodJob # :nodoc:
|
|
170
170
|
# @return [Integer]
|
171
171
|
def ready_worker_count
|
172
172
|
synchronize do
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
173
|
+
if Concurrent.on_jruby?
|
174
|
+
@executor.getMaximumPoolSize - @executor.getActiveCount
|
175
|
+
else
|
176
|
+
workers_still_to_be_created = @max_length - @pool.length
|
177
|
+
workers_created_but_waiting = @ready.length
|
178
|
+
workers_still_to_be_created + workers_created_but_waiting
|
179
|
+
end
|
177
180
|
end
|
178
181
|
end
|
179
182
|
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.4.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-
|
11
|
+
date: 2020-12-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 1.0.2
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: pg
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 1.0.0
|
62
|
-
type: :runtime
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: 1.0.0
|
69
55
|
- !ruby/object:Gem::Dependency
|
70
56
|
name: railties
|
71
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,20 +150,6 @@ dependencies:
|
|
164
150
|
- - ">="
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
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
153
|
- !ruby/object:Gem::Dependency
|
182
154
|
name: foreman
|
183
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -248,20 +220,6 @@ dependencies:
|
|
248
220
|
- - ">="
|
249
221
|
- !ruby/object:Gem::Version
|
250
222
|
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
223
|
- !ruby/object:Gem::Dependency
|
266
224
|
name: pry-rails
|
267
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -290,34 +248,6 @@ dependencies:
|
|
290
248
|
- - ">="
|
291
249
|
- !ruby/object:Gem::Version
|
292
250
|
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'
|
307
|
-
- !ruby/object:Gem::Dependency
|
308
|
-
name: rbtrace
|
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
251
|
- !ruby/object:Gem::Dependency
|
322
252
|
name: rspec-rails
|
323
253
|
requirement: !ruby/object:Gem::Requirement
|
@@ -332,62 +262,6 @@ dependencies:
|
|
332
262
|
- - ">="
|
333
263
|
- !ruby/object:Gem::Version
|
334
264
|
version: '0'
|
335
|
-
- !ruby/object:Gem::Dependency
|
336
|
-
name: rubocop
|
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-performance
|
351
|
-
requirement: !ruby/object:Gem::Requirement
|
352
|
-
requirements:
|
353
|
-
- - ">="
|
354
|
-
- !ruby/object:Gem::Version
|
355
|
-
version: '0'
|
356
|
-
type: :development
|
357
|
-
prerelease: false
|
358
|
-
version_requirements: !ruby/object:Gem::Requirement
|
359
|
-
requirements:
|
360
|
-
- - ">="
|
361
|
-
- !ruby/object:Gem::Version
|
362
|
-
version: '0'
|
363
|
-
- !ruby/object:Gem::Dependency
|
364
|
-
name: rubocop-rails
|
365
|
-
requirement: !ruby/object:Gem::Requirement
|
366
|
-
requirements:
|
367
|
-
- - ">="
|
368
|
-
- !ruby/object:Gem::Version
|
369
|
-
version: '0'
|
370
|
-
type: :development
|
371
|
-
prerelease: false
|
372
|
-
version_requirements: !ruby/object:Gem::Requirement
|
373
|
-
requirements:
|
374
|
-
- - ">="
|
375
|
-
- !ruby/object:Gem::Version
|
376
|
-
version: '0'
|
377
|
-
- !ruby/object:Gem::Dependency
|
378
|
-
name: rubocop-rspec
|
379
|
-
requirement: !ruby/object:Gem::Requirement
|
380
|
-
requirements:
|
381
|
-
- - ">="
|
382
|
-
- !ruby/object:Gem::Version
|
383
|
-
version: '0'
|
384
|
-
type: :development
|
385
|
-
prerelease: false
|
386
|
-
version_requirements: !ruby/object:Gem::Requirement
|
387
|
-
requirements:
|
388
|
-
- - ">="
|
389
|
-
- !ruby/object:Gem::Version
|
390
|
-
version: '0'
|
391
265
|
- !ruby/object:Gem::Dependency
|
392
266
|
name: selenium-webdriver
|
393
267
|
requirement: !ruby/object:Gem::Requirement
|
@@ -458,20 +332,20 @@ files:
|
|
458
332
|
- CHANGELOG.md
|
459
333
|
- LICENSE.txt
|
460
334
|
- README.md
|
335
|
+
- engine/app/assets/style.css
|
336
|
+
- engine/app/assets/vendor/bootstrap/bootstrap-native.js
|
337
|
+
- engine/app/assets/vendor/bootstrap/bootstrap.css
|
338
|
+
- engine/app/assets/vendor/chartist/chartist.css
|
339
|
+
- engine/app/assets/vendor/chartist/chartist.js
|
461
340
|
- engine/app/controllers/good_job/active_jobs_controller.rb
|
462
341
|
- engine/app/controllers/good_job/base_controller.rb
|
463
342
|
- engine/app/controllers/good_job/dashboards_controller.rb
|
464
343
|
- engine/app/helpers/good_job/application_helper.rb
|
465
|
-
- engine/app/views/assets/_style.css.erb
|
466
344
|
- engine/app/views/good_job/active_jobs/show.html.erb
|
467
345
|
- engine/app/views/good_job/dashboards/index.html.erb
|
468
346
|
- engine/app/views/layouts/good_job/base.html.erb
|
469
347
|
- engine/app/views/shared/_chart.erb
|
470
348
|
- engine/app/views/shared/_jobs_table.erb
|
471
|
-
- engine/app/views/vendor/bootstrap/_bootstrap-native.js.erb
|
472
|
-
- engine/app/views/vendor/bootstrap/_bootstrap.css.erb
|
473
|
-
- engine/app/views/vendor/chartist/_chartist.css.erb
|
474
|
-
- engine/app/views/vendor/chartist/_chartist.js.erb
|
475
349
|
- engine/config/routes.rb
|
476
350
|
- engine/lib/good_job/engine.rb
|
477
351
|
- exe/good_job
|
@@ -525,7 +399,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
525
399
|
- !ruby/object:Gem::Version
|
526
400
|
version: '0'
|
527
401
|
requirements: []
|
528
|
-
rubygems_version: 3.
|
402
|
+
rubygems_version: 3.2.3
|
529
403
|
signing_key:
|
530
404
|
specification_version: 4
|
531
405
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|