good_job 1.3.1 → 1.3.6
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 -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 +43 -11
- data/lib/good_job/log_subscriber.rb +7 -7
- data/lib/good_job/multi_scheduler.rb +14 -5
- data/lib/good_job/notifier.rb +19 -14
- data/lib/good_job/version.rb +1 -1
- metadata +8 -92
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b5d625792328475faca2679c6718f344fc5162cac639be09f91197c998f2fdf
|
4
|
+
data.tar.gz: 4aef2c4ae814f2a0606c18e875c91e56f8a1411394c37d45f4e1141be719f080
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d309e288c89c65ec809cf7f9e138026b229aed9abb6ee667343c56ed1e96fb0833be36b7e1f1c6b064168957f537d572e2ceb5ecad0f6e754108762ace9fbd3b
|
7
|
+
data.tar.gz: c1b83122c14edcbe619c6198fae270f9dbb0398888232f96e8956be23ca0f5798422181a21ea08735a0089ad3a92c0405ee69731a8a205f3fe9c99ba2b27bb36
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,71 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v1.3.6](https://github.com/bensheldon/good_job/tree/v1.3.6) (2020-12-30)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.5...v1.3.6)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Call GoodJob.on\_thread\_error when Notifier thread raises exception [\#185](https://github.com/bensheldon/good_job/pull/185) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
- Improve dashboard UI, fix button state, add unfiltering [\#181](https://github.com/bensheldon/good_job/pull/181) ([bensheldon](https://github.com/bensheldon))
|
11
|
+
|
12
|
+
**Fixed bugs:**
|
13
|
+
|
14
|
+
- Replace ActiveRecord execute usage and avoid potential memory leakage [\#187](https://github.com/bensheldon/good_job/issues/187)
|
15
|
+
- Does good\_job hold on to advisory locks for finished jobs? [\#177](https://github.com/bensheldon/good_job/issues/177)
|
16
|
+
|
17
|
+
**Merged pull requests:**
|
18
|
+
|
19
|
+
- Run tests with Rails default configuration to enable Zeitwerk [\#190](https://github.com/bensheldon/good_job/pull/190) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
- 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))
|
21
|
+
- Have Lockable\#advisory\_locked? directly query pg\_locks table [\#188](https://github.com/bensheldon/good_job/pull/188) ([bensheldon](https://github.com/bensheldon))
|
22
|
+
- Update development gems, including Rails v6.1 and Rails HEAD [\#186](https://github.com/bensheldon/good_job/pull/186) ([bensheldon](https://github.com/bensheldon))
|
23
|
+
- Update Appraisals for Rails 6.1 [\#183](https://github.com/bensheldon/good_job/pull/183) ([bensheldon](https://github.com/bensheldon))
|
24
|
+
- Add Ruby 3 to CI test matrix [\#182](https://github.com/bensheldon/good_job/pull/182) ([bensheldon](https://github.com/bensheldon))
|
25
|
+
|
26
|
+
## [v1.3.5](https://github.com/bensheldon/good_job/tree/v1.3.5) (2020-12-17)
|
27
|
+
|
28
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.4...v1.3.5)
|
29
|
+
|
30
|
+
**Fixed bugs:**
|
31
|
+
|
32
|
+
- Ensure advisory lock CTE is MATERIALIZED on Postgres v12+ [\#179](https://github.com/bensheldon/good_job/pull/179) ([bensheldon](https://github.com/bensheldon))
|
33
|
+
- Ensure that deleted jobs are unlocked [\#178](https://github.com/bensheldon/good_job/pull/178) ([bensheldon](https://github.com/bensheldon))
|
34
|
+
|
35
|
+
**Closed issues:**
|
36
|
+
|
37
|
+
- not running jobs [\#168](https://github.com/bensheldon/good_job/issues/168)
|
38
|
+
- how to run good\_job on a separate machine [\#162](https://github.com/bensheldon/good_job/issues/162)
|
39
|
+
|
40
|
+
**Merged pull requests:**
|
41
|
+
|
42
|
+
- Add Appraisal for Rails 6.1-rc2 [\#175](https://github.com/bensheldon/good_job/pull/175) ([bensheldon](https://github.com/bensheldon))
|
43
|
+
|
44
|
+
## [v1.3.4](https://github.com/bensheldon/good_job/tree/v1.3.4) (2020-12-02)
|
45
|
+
|
46
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.3...v1.3.4)
|
47
|
+
|
48
|
+
**Fixed bugs:**
|
49
|
+
|
50
|
+
- Fix job ordering for Rails 6.1 [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
|
51
|
+
|
52
|
+
## [v1.3.3](https://github.com/bensheldon/good_job/tree/v1.3.3) (2020-12-01)
|
53
|
+
|
54
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.2...v1.3.3)
|
55
|
+
|
56
|
+
**Implemented enhancements:**
|
57
|
+
|
58
|
+
- UI: Admin UI with filters and space efficient layout [\#173](https://github.com/bensheldon/good_job/pull/173) ([zealot128](https://github.com/zealot128))
|
59
|
+
|
60
|
+
## [v1.3.2](https://github.com/bensheldon/good_job/tree/v1.3.2) (2020-11-12)
|
61
|
+
|
62
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.1...v1.3.2)
|
63
|
+
|
64
|
+
**Fixed bugs:**
|
65
|
+
|
66
|
+
- \(bug\) MultiScheduler polling bug [\#171](https://github.com/bensheldon/good_job/issues/171)
|
67
|
+
- MultiScheduler should delegate to all schedulers when state is nil [\#172](https://github.com/bensheldon/good_job/pull/172) ([bensheldon](https://github.com/bensheldon))
|
68
|
+
|
3
69
|
## [v1.3.1](https://github.com/bensheldon/good_job/tree/v1.3.1) (2020-11-01)
|
4
70
|
|
5
71
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.0...v1.3.1)
|
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,11 @@ 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
|
-
self.class.
|
160
|
+
self.class.connection.exec_query(query, 'GoodJob::Lockable Advisory Lock', [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]).any?
|
147
161
|
end
|
148
162
|
|
149
163
|
# Releases an advisory lock on this record if it is locked by this database
|
@@ -151,10 +165,11 @@ module GoodJob
|
|
151
165
|
# {#advisory_unlock} and {#advisory_lock} the same number of times.
|
152
166
|
# @return [Boolean] whether the lock was released.
|
153
167
|
def advisory_unlock
|
154
|
-
|
155
|
-
|
168
|
+
query = <<~SQL.squish
|
169
|
+
SELECT 1 AS one
|
170
|
+
WHERE pg_advisory_unlock(('x'||substr(md5($1 || $2::text), 1, 16))::bit(64)::bigint)
|
156
171
|
SQL
|
157
|
-
self.class.
|
172
|
+
self.class.connection.exec_query(query, 'GoodJob::Lockable Advisory Unlock', [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]).any?
|
158
173
|
end
|
159
174
|
|
160
175
|
# Acquires an advisory lock on this record or raises
|
@@ -191,13 +206,30 @@ module GoodJob
|
|
191
206
|
# Tests whether this record has an advisory lock on it.
|
192
207
|
# @return [Boolean]
|
193
208
|
def advisory_locked?
|
194
|
-
|
209
|
+
query = <<~SQL.squish
|
210
|
+
SELECT 1 AS one
|
211
|
+
FROM pg_locks
|
212
|
+
WHERE pg_locks.locktype = 'advisory'
|
213
|
+
AND pg_locks.objsubid = 1
|
214
|
+
AND pg_locks.classid = ('x' || substr(md5($1 || $2::text), 1, 16))::bit(32)::int
|
215
|
+
AND pg_locks.objid = (('x' || substr(md5($1 || $2::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
216
|
+
SQL
|
217
|
+
self.class.connection.exec_query(query, 'GoodJob::Lockable Advisory Locked?', [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]).any?
|
195
218
|
end
|
196
219
|
|
197
220
|
# Tests whether this record is locked by the current database session.
|
198
221
|
# @return [Boolean]
|
199
222
|
def owns_advisory_lock?
|
200
|
-
|
223
|
+
query = <<~SQL.squish
|
224
|
+
SELECT 1 AS one
|
225
|
+
FROM pg_locks
|
226
|
+
WHERE pg_locks.locktype = 'advisory'
|
227
|
+
AND pg_locks.objsubid = 1
|
228
|
+
AND pg_locks.classid = ('x' || substr(md5($1 || $2::text), 1, 16))::bit(32)::int
|
229
|
+
AND pg_locks.objid = (('x' || substr(md5($1 || $2::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
230
|
+
AND pg_locks.pid = pg_backend_pid()
|
231
|
+
SQL
|
232
|
+
self.class.connection.exec_query(query, 'GoodJob::Lockable Owns Advisory Lock?', [[nil, self.class.table_name], [nil, send(self.class.primary_key)]]).any?
|
201
233
|
end
|
202
234
|
|
203
235
|
# Releases all advisory locks on the record that are held by the current
|
@@ -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
|
@@ -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
@@ -90,6 +90,18 @@ module GoodJob # :nodoc:
|
|
90
90
|
!@pool.running?
|
91
91
|
end
|
92
92
|
|
93
|
+
# Invoked on completion of ThreadPoolExecutor task
|
94
|
+
# @!visibility private
|
95
|
+
# @return [void]
|
96
|
+
def listen_observer(_time, _result, thread_error)
|
97
|
+
if thread_error
|
98
|
+
GoodJob.on_thread_error.call(thread_error) if GoodJob.on_thread_error.respond_to?(:call)
|
99
|
+
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
|
100
|
+
end
|
101
|
+
|
102
|
+
listen unless shutdown?
|
103
|
+
end
|
104
|
+
|
93
105
|
private
|
94
106
|
|
95
107
|
def create_pool
|
@@ -100,7 +112,7 @@ module GoodJob # :nodoc:
|
|
100
112
|
future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
|
101
113
|
with_listen_connection do |conn|
|
102
114
|
ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
|
103
|
-
conn.async_exec
|
115
|
+
conn.async_exec("LISTEN #{CHANNEL}").clear
|
104
116
|
end
|
105
117
|
|
106
118
|
ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
|
@@ -120,14 +132,11 @@ module GoodJob # :nodoc:
|
|
120
132
|
listening.make_false
|
121
133
|
end
|
122
134
|
end
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
@listening.make_false
|
129
|
-
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
130
|
-
conn.async_exec "UNLISTEN *"
|
135
|
+
ensure
|
136
|
+
listening.make_false
|
137
|
+
ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
|
138
|
+
conn.async_exec("UNLISTEN *").clear
|
139
|
+
end
|
131
140
|
end
|
132
141
|
end
|
133
142
|
|
@@ -135,16 +144,12 @@ module GoodJob # :nodoc:
|
|
135
144
|
future.execute
|
136
145
|
end
|
137
146
|
|
138
|
-
def listen_observer(_time, _result, _thread_error)
|
139
|
-
listen unless shutdown?
|
140
|
-
end
|
141
|
-
|
142
147
|
def with_listen_connection
|
143
148
|
ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
|
144
149
|
ActiveRecord::Base.connection_pool.remove(conn)
|
145
150
|
end
|
146
151
|
pg_conn = ar_conn.raw_connection
|
147
|
-
pg_conn.
|
152
|
+
pg_conn.async_exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}").clear
|
148
153
|
yield pg_conn
|
149
154
|
ensure
|
150
155
|
ar_conn&.disconnect!
|
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.6
|
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-30 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
|
@@ -332,62 +304,6 @@ dependencies:
|
|
332
304
|
- - ">="
|
333
305
|
- !ruby/object:Gem::Version
|
334
306
|
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
307
|
- !ruby/object:Gem::Dependency
|
392
308
|
name: selenium-webdriver
|
393
309
|
requirement: !ruby/object:Gem::Requirement
|
@@ -458,20 +374,20 @@ files:
|
|
458
374
|
- CHANGELOG.md
|
459
375
|
- LICENSE.txt
|
460
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
|
461
382
|
- engine/app/controllers/good_job/active_jobs_controller.rb
|
462
383
|
- engine/app/controllers/good_job/base_controller.rb
|
463
384
|
- engine/app/controllers/good_job/dashboards_controller.rb
|
464
385
|
- engine/app/helpers/good_job/application_helper.rb
|
465
|
-
- engine/app/views/assets/_style.css.erb
|
466
386
|
- engine/app/views/good_job/active_jobs/show.html.erb
|
467
387
|
- engine/app/views/good_job/dashboards/index.html.erb
|
468
388
|
- engine/app/views/layouts/good_job/base.html.erb
|
469
389
|
- engine/app/views/shared/_chart.erb
|
470
390
|
- 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
391
|
- engine/config/routes.rb
|
476
392
|
- engine/lib/good_job/engine.rb
|
477
393
|
- exe/good_job
|
@@ -525,7 +441,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
525
441
|
- !ruby/object:Gem::Version
|
526
442
|
version: '0'
|
527
443
|
requirements: []
|
528
|
-
rubygems_version: 3.
|
444
|
+
rubygems_version: 3.2.3
|
529
445
|
signing_key:
|
530
446
|
specification_version: 4
|
531
447
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|