good_job 1.3.3 → 1.4.1

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: d48414f6b03482087018157412c87afb3fdb70929a292294f7db2e14309c31fd
4
- data.tar.gz: dd221c88385350fff14b593a6504bc98adeae44893d523c90949332a81eb6fd9
3
+ metadata.gz: ac71f85784bd491d995252ed498d55642ee511a402ad093cc056fd2e062a437b
4
+ data.tar.gz: 33f1a9ebf6952f3140046338bb616a4a81c469b4099f526e56d15b9aa1f8951d
5
5
  SHA512:
6
- metadata.gz: 77ab00b0cd0772641a402205856d5c03c675dc2edc769e5fed8909636fc2c4a69b033da98a7d0ae09a7ac1fc3f40dd5dc4ff637b3d13e4c290acb9a40bc51ead
7
- data.tar.gz: 1777e663267f7e626d7f5ebf5b61b156e83b7bcf9234addddabeb10ef1054c05f8602ca0111c1abe4de9fb53b32aafac4df5d88e1c7c0147e0f1a7cbdc57219d
6
+ metadata.gz: 1660dd44eca81ce42cbdb917253dfb340d5e5a4b371275f51eb919fc5aacb3485c6bbd87164d49d07547682efe311fb1a41bce35599ab5d45b8bd669d1e840c2
7
+ data.tar.gz: 1c2994d95046ea9f692539dffcb0bfa33d81daed73886a2fef7ab9c9c6063b4085c17f8cc7ed93bcfab999ca25ffef1db31fa82344e3a31a6dcbdc3a84c966ed
@@ -1,10 +1,83 @@
1
1
  # Changelog
2
2
 
3
+ ## [v1.4.1](https://github.com/bensheldon/good_job/tree/v1.4.1) (2021-01-09)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.4.0...v1.4.1)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Do not add lib/generators to Zeitwerk autoloader [\#192](https://github.com/bensheldon/good_job/pull/192) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Closed issues:**
12
+
13
+ - Issues with Heroku and Good Job [\#184](https://github.com/bensheldon/good_job/issues/184)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - Add missing YARD docs and Dashboard screenshot [\#191](https://github.com/bensheldon/good_job/pull/191) ([bensheldon](https://github.com/bensheldon))
18
+
19
+ ## [v1.4.0](https://github.com/bensheldon/good_job/tree/v1.4.0) (2020-12-31)
20
+
21
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.6...v1.4.0)
22
+
23
+ **Implemented enhancements:**
24
+
25
+ - Add JRuby support [\#167](https://github.com/bensheldon/good_job/pull/167) ([bensheldon](https://github.com/bensheldon))
26
+
27
+ ## [v1.3.6](https://github.com/bensheldon/good_job/tree/v1.3.6) (2020-12-30)
28
+
29
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.5...v1.3.6)
30
+
31
+ **Implemented enhancements:**
32
+
33
+ - Call GoodJob.on\_thread\_error when Notifier thread raises exception [\#185](https://github.com/bensheldon/good_job/pull/185) ([bensheldon](https://github.com/bensheldon))
34
+ - Improve dashboard UI, fix button state, add unfiltering [\#181](https://github.com/bensheldon/good_job/pull/181) ([bensheldon](https://github.com/bensheldon))
35
+
36
+ **Fixed bugs:**
37
+
38
+ - Replace ActiveRecord execute usage and avoid potential memory leakage [\#187](https://github.com/bensheldon/good_job/issues/187)
39
+ - Does good\_job hold on to advisory locks for finished jobs? [\#177](https://github.com/bensheldon/good_job/issues/177)
40
+
41
+ **Merged pull requests:**
42
+
43
+ - Run tests with Rails default configuration to enable Zeitwerk [\#190](https://github.com/bensheldon/good_job/pull/190) ([bensheldon](https://github.com/bensheldon))
44
+ - 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))
45
+ - Have Lockable\#advisory\_locked? directly query pg\_locks table [\#188](https://github.com/bensheldon/good_job/pull/188) ([bensheldon](https://github.com/bensheldon))
46
+ - Update development gems, including Rails v6.1 and Rails HEAD [\#186](https://github.com/bensheldon/good_job/pull/186) ([bensheldon](https://github.com/bensheldon))
47
+ - Update Appraisals for Rails 6.1 [\#183](https://github.com/bensheldon/good_job/pull/183) ([bensheldon](https://github.com/bensheldon))
48
+ - Add Ruby 3 to CI test matrix [\#182](https://github.com/bensheldon/good_job/pull/182) ([bensheldon](https://github.com/bensheldon))
49
+
50
+ ## [v1.3.5](https://github.com/bensheldon/good_job/tree/v1.3.5) (2020-12-17)
51
+
52
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.4...v1.3.5)
53
+
54
+ **Fixed bugs:**
55
+
56
+ - Ensure advisory lock CTE is MATERIALIZED on Postgres v12+ [\#179](https://github.com/bensheldon/good_job/pull/179) ([bensheldon](https://github.com/bensheldon))
57
+ - Ensure that deleted jobs are unlocked [\#178](https://github.com/bensheldon/good_job/pull/178) ([bensheldon](https://github.com/bensheldon))
58
+
59
+ **Closed issues:**
60
+
61
+ - not running jobs [\#168](https://github.com/bensheldon/good_job/issues/168)
62
+ - how to run good\_job on a separate machine [\#162](https://github.com/bensheldon/good_job/issues/162)
63
+
64
+ **Merged pull requests:**
65
+
66
+ - Add Appraisal for Rails 6.1-rc2 [\#175](https://github.com/bensheldon/good_job/pull/175) ([bensheldon](https://github.com/bensheldon))
67
+
68
+ ## [v1.3.4](https://github.com/bensheldon/good_job/tree/v1.3.4) (2020-12-02)
69
+
70
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.3...v1.3.4)
71
+
72
+ **Fixed bugs:**
73
+
74
+ - Fix job ordering for Rails 6.1 [\#174](https://github.com/bensheldon/good_job/pull/174) ([morgoth](https://github.com/morgoth))
75
+
3
76
  ## [v1.3.3](https://github.com/bensheldon/good_job/tree/v1.3.3) (2020-12-01)
4
77
 
5
78
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v1.3.2...v1.3.3)
6
79
 
7
- **Merged pull requests:**
80
+ **Implemented enhancements:**
8
81
 
9
82
  - UI: Admin UI with filters and space efficient layout [\#173](https://github.com/bensheldon/good_job/pull/173) ([zealot128](https://github.com/zealot128))
10
83
 
@@ -51,9 +124,6 @@
51
124
  **Implemented enhancements:**
52
125
 
53
126
  - Lengthen default poll interval from 1 to 5 seconds [\#156](https://github.com/bensheldon/good_job/pull/156) ([bensheldon](https://github.com/bensheldon))
54
-
55
- **Merged pull requests:**
56
-
57
127
  - Rename reperform\_jobs\_on\_standard\_error to retry\_on\_unhandled\_error [\#154](https://github.com/bensheldon/good_job/pull/154) ([morgoth](https://github.com/morgoth))
58
128
 
59
129
  ## [v1.2.6](https://github.com/bensheldon/good_job/tree/v1.2.6) (2020-09-29)
@@ -155,7 +225,6 @@
155
225
  **Merged pull requests:**
156
226
 
157
227
  - stop depending on all rails libs [\#104](https://github.com/bensheldon/good_job/pull/104) ([thilo](https://github.com/thilo))
158
- - Use more ActiveRecord in Lockable and not connection.execute [\#102](https://github.com/bensheldon/good_job/pull/102) ([bensheldon](https://github.com/bensheldon))
159
228
 
160
229
  ## [v1.2.2](https://github.com/bensheldon/good_job/tree/v1.2.2) (2020-08-27)
161
230
 
@@ -164,11 +233,13 @@
164
233
  **Implemented enhancements:**
165
234
 
166
235
  - Run Github Action tests against Ruby 2.5, 2.6, 2.7 [\#100](https://github.com/bensheldon/good_job/issues/100)
236
+ - Name the thread pools [\#96](https://github.com/bensheldon/good_job/pull/96) ([sj26](https://github.com/sj26))
167
237
 
168
238
  **Fixed bugs:**
169
239
 
170
240
  - Freezes puma on code change [\#95](https://github.com/bensheldon/good_job/issues/95)
171
241
  - Ruby 2.7 keyword arguments warning [\#93](https://github.com/bensheldon/good_job/issues/93)
242
+ - Return to using executor.wrap around Scheduler execution task [\#99](https://github.com/bensheldon/good_job/pull/99) ([bensheldon](https://github.com/bensheldon))
172
243
 
173
244
  **Closed issues:**
174
245
 
@@ -176,11 +247,10 @@
176
247
 
177
248
  **Merged pull requests:**
178
249
 
250
+ - Use more ActiveRecord in Lockable and not connection.execute [\#102](https://github.com/bensheldon/good_job/pull/102) ([bensheldon](https://github.com/bensheldon))
179
251
  - Run CI tests on Ruby 2.5, 2.6, and 2.7 [\#101](https://github.com/bensheldon/good_job/pull/101) ([arku](https://github.com/arku))
180
- - Return to using executor.wrap around Scheduler execution task [\#99](https://github.com/bensheldon/good_job/pull/99) ([bensheldon](https://github.com/bensheldon))
181
252
  - Fix Ruby 2.7 keyword arguments warning [\#98](https://github.com/bensheldon/good_job/pull/98) ([arku](https://github.com/arku))
182
253
  - Remove executor/reloader for less interlocking [\#97](https://github.com/bensheldon/good_job/pull/97) ([sj26](https://github.com/sj26))
183
- - Name the thread pools [\#96](https://github.com/bensheldon/good_job/pull/96) ([sj26](https://github.com/sj26))
184
254
  - Add test for `rails g good\_job:install` [\#94](https://github.com/bensheldon/good_job/pull/94) ([arku](https://github.com/arku))
185
255
 
186
256
  ## [v1.2.1](https://github.com/bensheldon/good_job/tree/v1.2.1) (2020-08-21)
@@ -438,6 +508,7 @@
438
508
  - Add pg gem as explicit dependency [\#13](https://github.com/bensheldon/good_job/pull/13) ([bensheldon](https://github.com/bensheldon))
439
509
  - Bump nokogiri from 1.10.7 to 1.10.9 [\#12](https://github.com/bensheldon/good_job/pull/12) ([dependabot[bot]](https://github.com/apps/dependabot))
440
510
  - Add Appraisal with tests for Rails 5.1, 5.2, 6.0 [\#11](https://github.com/bensheldon/good_job/pull/11) ([bensheldon](https://github.com/bensheldon))
511
+ - Use Rails.logger and ActiveSupport::Notifications for logging instead of puts [\#10](https://github.com/bensheldon/good_job/pull/10) ([bensheldon](https://github.com/bensheldon))
441
512
 
442
513
  ## [v0.2.0](https://github.com/bensheldon/good_job/tree/v0.2.0) (2020-03-06)
443
514
 
@@ -445,7 +516,6 @@
445
516
 
446
517
  **Merged pull requests:**
447
518
 
448
- - Use Rails.logger and ActiveSupport::Notifications for logging instead of puts [\#10](https://github.com/bensheldon/good_job/pull/10) ([bensheldon](https://github.com/bensheldon))
449
519
  - Remove minitest files [\#9](https://github.com/bensheldon/good_job/pull/9) ([bensheldon](https://github.com/bensheldon))
450
520
  - Use scheduled\_at and priority for scheduling [\#8](https://github.com/bensheldon/good_job/pull/8) ([bensheldon](https://github.com/bensheldon))
451
521
  - Create Github Action workflow for PRs and Issues [\#7](https://github.com/bensheldon/good_job/pull/7) ([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
@@ -227,6 +234,8 @@ GoodJob.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
227
234
 
228
235
  ### Dashboard
229
236
 
237
+ ![Dashboard UI](https://github.com/bensheldon/good_job/raw/main/SCREENSHOT.png)
238
+
230
239
  _🚧 GoodJob's dashboard is a work in progress. Please contribute ideas and code on [Github](https://github.com/bensheldon/good_job/issues)._
231
240
 
232
241
  GoodJob includes a Dashboard as a mountable `Rails::Engine`.
@@ -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
@@ -42,11 +42,11 @@ module GoodJob
42
42
  GoodJob::Job.group("serialized_params->>'job_class'").count
43
43
  end
44
44
 
45
- def to_query(override)
45
+ def to_params(override)
46
46
  {
47
47
  state: params[:state],
48
48
  job_class: params[:job_class],
49
- }.merge(override).delete_if { |_, v| v.nil? }.to_query
49
+ }.merge(override).delete_if { |_, v| v.nil? }
50
50
  end
51
51
  end
52
52
 
@@ -8,18 +8,30 @@
8
8
  <small>Filter by job class</small>
9
9
  <br>
10
10
  <% @filter.job_classes.each do |name, count| %>
11
- <a href='<%= request.path + "?#{@filter.to_query(job_class: name)}" %>' class='btn btn-sm btn-outline-secondary <%= "active" if params[:job_class] == name %>'>
12
- <%= name %> (<%= count %>)
13
- </a>
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 %>
14
20
  <% end %>
15
21
  </div>
16
22
  <div>
17
23
  <small>Filter by state</small>
18
24
  <br>
19
25
  <% @filter.states.each do |name, count| %>
20
- <a href='<%= request.path + "?#{@filter.to_query(state: name)}" %>' class='btn btn-sm btn-outline-secondary <%= "active" if params[:state] == name %>'>
21
- <%= name %> (<%= count %>)
22
- </a>
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 %>
23
35
  <% end %>
24
36
  </div>
25
37
  </div>
@@ -28,7 +40,7 @@
28
40
  <% if @filter.jobs.present? %>
29
41
  <%= render 'shared/jobs_table', jobs: @filter.jobs %>
30
42
 
31
- <nav aria-label="Job pagination">
43
+ <nav aria-label="Job pagination" class="mt-3">
32
44
  <ul class="pagination">
33
45
  <li class="page-item">
34
46
  <%= link_to({ after_scheduled_at: (@filter.last.scheduled_at || @filter.last.created_at), after_id: @filter.last.id }, class: "page-link") do %>
@@ -1,19 +1,19 @@
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>
@@ -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 table-sm">
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>
@@ -1,16 +1,15 @@
1
1
  require "rails"
2
-
3
2
  require "active_job"
4
3
  require "active_job/queue_adapters"
5
4
 
6
5
  require "zeitwerk"
7
-
8
- loader = Zeitwerk::Loader.for_gem
9
- loader.inflector.inflect(
10
- 'cli' => "CLI"
11
- )
12
- loader.push_dir(File.join(__dir__, ["generators"]))
13
- loader.setup
6
+ Zeitwerk::Loader.for_gem.tap do |loader|
7
+ loader.inflector.inflect({
8
+ "cli" => "CLI",
9
+ })
10
+ loader.ignore(File.join(File.dirname(__FILE__), "generators"))
11
+ loader.setup
12
+ end
14
13
 
15
14
  require "good_job/railtie"
16
15
 
@@ -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
@@ -103,6 +101,9 @@ module GoodJob
103
101
  ).to_i
104
102
  end
105
103
 
104
+ # Number of seconds to preserve jobs when using the +good_job cleanup_preserved_jobs+ CLI command.
105
+ # This configuration is only used when {GoodJob.preserve_job_records} is +true+.
106
+ # @return [Boolean]
106
107
  def cleanup_preserved_jobs_before_seconds_ago
107
108
  (
108
109
  options[:before_seconds_ago] ||
@@ -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,12 @@ 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
+ 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
- where_sql = <<~SQL.squish
155
- pg_advisory_unlock(('x' || substr(md5(:table_name || :id::text), 1, 16))::bit(64)::bigint)
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
- self.class.unscoped.exists?([where_sql, { table_name: self.class.table_name, id: send(self.class.primary_key) }])
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
- self.class.unscoped.advisory_locked.exists?(id: send(self.class.primary_key))
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
- self.class.unscoped.owns_advisory_locked.exists?(id: send(self.class.primary_key))
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
@@ -9,6 +9,9 @@ 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
+ # Raised if the Database adapter does not implement LISTEN.
13
+ AdapterCannotListenError = Class.new(StandardError)
14
+
12
15
  # Default Postgres channel for LISTEN/NOTIFY
13
16
  CHANNEL = 'good_job'.freeze
14
17
  # Defaults for instance of Concurrent::ThreadPoolExecutor
@@ -90,6 +93,20 @@ module GoodJob # :nodoc:
90
93
  !@pool.running?
91
94
  end
92
95
 
96
+ # Invoked on completion of ThreadPoolExecutor task
97
+ # @!visibility private
98
+ # @return [void]
99
+ def listen_observer(_time, _result, thread_error)
100
+ return if thread_error.is_a? AdapterCannotListenError
101
+
102
+ if thread_error
103
+ GoodJob.on_thread_error.call(thread_error) if GoodJob.on_thread_error.respond_to?(:call)
104
+ ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
105
+ end
106
+
107
+ listen unless shutdown?
108
+ end
109
+
93
110
  private
94
111
 
95
112
  def create_pool
@@ -100,7 +117,7 @@ module GoodJob # :nodoc:
100
117
  future = Concurrent::Future.new(args: [@recipients, @pool, @listening], executor: @pool) do |recipients, pool, listening|
101
118
  with_listen_connection do |conn|
102
119
  ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
103
- conn.async_exec "LISTEN #{CHANNEL}"
120
+ conn.async_exec("LISTEN #{CHANNEL}").clear
104
121
  end
105
122
 
106
123
  ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
@@ -120,14 +137,11 @@ module GoodJob # :nodoc:
120
137
  listening.make_false
121
138
  end
122
139
  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 *"
140
+ ensure
141
+ listening.make_false
142
+ ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
143
+ conn.async_exec("UNLISTEN *").clear
144
+ end
131
145
  end
132
146
  end
133
147
 
@@ -135,16 +149,14 @@ module GoodJob # :nodoc:
135
149
  future.execute
136
150
  end
137
151
 
138
- def listen_observer(_time, _result, _thread_error)
139
- listen unless shutdown?
140
- end
141
-
142
152
  def with_listen_connection
143
153
  ar_conn = ActiveRecord::Base.connection_pool.checkout.tap do |conn|
144
154
  ActiveRecord::Base.connection_pool.remove(conn)
145
155
  end
146
156
  pg_conn = ar_conn.raw_connection
147
- pg_conn.exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}")
157
+ raise AdapterCannotListenError unless pg_conn.respond_to? :wait_for_notify
158
+
159
+ pg_conn.async_exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}").clear
148
160
  yield pg_conn
149
161
  ensure
150
162
  ar_conn&.disconnect!
@@ -19,6 +19,9 @@ module GoodJob # :nodoc:
19
19
  # @return [array<GoodJob:Poller>]
20
20
  cattr_reader :instances, default: [], instance_reader: false
21
21
 
22
+ # Creates GoodJob::Poller from a GoodJob::Configuration instance.
23
+ # @param configuration [GoodJob::Configuration]
24
+ # @return [GoodJob::Poller]
22
25
  def self.from_configuration(configuration)
23
26
  GoodJob::Poller.new(poll_interval: configuration.poll_interval)
24
27
  end
@@ -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: -1,
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
- workers_still_to_be_created = @max_length - @pool.length
174
- workers_created_but_waiting = @ready.length
175
-
176
- workers_still_to_be_created + workers_created_but_waiting
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
@@ -1,4 +1,4 @@
1
1
  module GoodJob
2
2
  # GoodJob gem version.
3
- VERSION = '1.3.3'.freeze
3
+ VERSION = '1.4.1'.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.3
4
+ version: 1.4.1
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-12-01 00:00:00.000000000 Z
11
+ date: 2021-01-09 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.1.4
402
+ rubygems_version: 3.2.4
529
403
  signing_key:
530
404
  specification_version: 4
531
405
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails