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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce89780b94c2a1f01cff7b85b3bdb066988a50c1b55ecef909d69ed0dbce5b00
4
- data.tar.gz: e68b2a0b1283e79d30d8dd49e13a8f01fea53e7cf99d60aa9ab243826177838a
3
+ metadata.gz: 8b5d625792328475faca2679c6718f344fc5162cac639be09f91197c998f2fdf
4
+ data.tar.gz: 4aef2c4ae814f2a0606c18e875c91e56f8a1411394c37d45f4e1141be719f080
5
5
  SHA512:
6
- metadata.gz: 1332dd583ab8d0d1e359b85c0f03d082a2407f0ef825d419aae4986e003a18a9adfce9e253aaa5a386497b1a0fc941f732968caac3a813c78f079c23fae2bd72
7
- data.tar.gz: 1650338a713a12387b15241eb5d8808ac9fbf43fa780645c1886612ba0590ca392f2dbd93e6c8a82e5483ddb3e3ba60d0c4e04695c6155ae3993d82af42bbef7
6
+ metadata.gz: d309e288c89c65ec809cf7f9e138026b229aed9abb6ee667343c56ed1e96fb0833be36b7e1f1c6b064168957f537d572e2ceb5ecad0f6e754108762ace9fbd3b
7
+ data.tar.gz: c1b83122c14edcbe619c6198fae270f9dbb0398888232f96e8956be23ca0f5798422181a21ea08735a0089ad3a92c0405ee69731a8a205f3fe9c99ba2b27bb36
@@ -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)
@@ -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('COALESCE(scheduled_at, created_at) DESC')
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
- def index
4
- @jobs = GoodJob::Job.display_all(after_scheduled_at: params[:after_scheduled_at], after_id: params[:after_id])
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
- <% if @jobs.present? %>
6
- <%= render 'shared/jobs_table', jobs: @jobs %>
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: (@jobs.last.scheduled_at || @jobs.last.created_at), after_id: @jobs.last.id }, class: "page-link") do %>
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">&raquo;</span>
13
48
  <% end %>
14
49
  </li>
@@ -1,24 +1,24 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Good job</title>
4
+ <title>Good Job Dashboard</title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
7
 
8
8
  <style>
9
- <%= render "vendor/bootstrap/bootstrap.css" %>
10
- <%= render "vendor/chartist/chartist.css" %>
11
- <%= render "assets/style.css" %>
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/bootstrap/bootstrap-native.js" %>
16
- <%= render "vendor/chartist/chartist.js" %>
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,7 +1,7 @@
1
1
  <div id="chart"></div>
2
2
 
3
3
  <script>
4
- new Chartist.Line('#chart', <%= raw chart_data.to_json %>, {
4
+ new Chartist.Line('#chart', <%== chart_data.to_json %>, {
5
5
  height: '300px',
6
6
  fullWidth: true,
7
7
  chartPadding: {
@@ -1,26 +1,28 @@
1
- <div class="table-responsive">
2
- <table class="table table-bordered table-hover">
3
- <thead>
4
- <th>GoodJob ID</th>
5
- <th>ActiveJob ID</th>
6
- <th>Job Class</th>
7
- <th>Queue</th>
8
- <th>Scheduled At</th>
9
- <th>Error</th>
10
- <th>ActiveJob Params</th>
11
- </thead>
12
- <tbody>
13
- <% jobs.each do |job| %>
14
- <tr id="<%= dom_id(job) %>">
15
- <td><%= link_to job.id, active_job_path(job.serialized_params['job_id'], anchor: dom_id(job)) %></td>
16
- <td><%= link_to job.serialized_params['job_id'], active_job_path(job.serialized_params['job_id']) %></td>
17
- <td><%= job.serialized_params['job_class'] %></td>
18
- <td><%= job.queue_name %></td>
19
- <td><%= job.scheduled_at || job.created_at %></td>
20
- <td><%= job.error %></td>
21
- <td><pre><%= JSON.pretty_generate(job.serialized_params) %></pre></td>
22
- </tr>
23
- <% end %>
24
- </tbody>
25
- </table>
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>
@@ -58,9 +58,7 @@ module GoodJob
58
58
  def rails_execution_mode
59
59
  if execution_mode(default: nil)
60
60
  execution_mode
61
- elsif Rails.env.development?
62
- :inline
63
- elsif Rails.env.test?
61
+ elsif Rails.env.development? || Rails.env.test?
64
62
  :inline
65
63
  else
66
64
  :external
@@ -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&.owns_advisory_lock?
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
@@ -32,11 +32,18 @@ module GoodJob
32
32
  original_query = self
33
33
 
34
34
  cte_table = Arel::Table.new(:rows)
35
- composed_cte = Arel::Nodes::As.new(cte_table, original_query.select(primary_key).except(:limit).arel)
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
- .with(composed_cte)
39
- .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 }])))
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
- where_sql = <<~SQL.squish
144
- pg_try_advisory_lock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
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.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }])
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
- where_sql = <<~SQL.squish
155
- pg_advisory_unlock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
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.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }])
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
- self.class.unscoped.advisory_locked.exists?(id: send(self.class.primary_key))
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
- self.class.unscoped.owns_advisory_locked.exists?(id: send(self.class.primary_key))
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
- any_true = schedulers.any? do |scheduler|
30
- scheduler.create_thread(state).tap { |result| results << result }
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 any_true
40
+ if results.any?
34
41
  true
35
- else
36
- results.any? { |result| result == false } ? false : nil
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
@@ -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 "LISTEN #{CHANNEL}"
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
- end
124
- rescue StandardError => e
125
- ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: e })
126
- raise
127
- ensure
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.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
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!
@@ -1,4 +1,4 @@
1
1
  module GoodJob
2
2
  # GoodJob gem version.
3
- VERSION = '1.3.1'.freeze
3
+ VERSION = '1.3.6'.freeze
4
4
  end
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.1
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-01 00:00:00.000000000 Z
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.1.4
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