good_job 2.7.3 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6f10b3664d49f86aef9b010af1ca673fc34235966ae98c55fed5ba271bcaae2
4
- data.tar.gz: bd7f84c9c0b75342ea10d72ad40d7fba4cd51aa8105348e362587de16452a19b
3
+ metadata.gz: ba4a26a2047c0e7dd8b78264fee7c6d2c05c2dd59e38f1df402b698826b56288
4
+ data.tar.gz: 3bd4c49139941c2d37751abb9d32808b0efd7a33370aa7370afeed21cc845ab0
5
5
  SHA512:
6
- metadata.gz: 9a1de45c7584ec2d55b54187ee818fdb0c58a954463425a5c5394e097fb7a7236c02219f6cdbbf26168103bd359ed1b72f5cbb096b8d3ae5a524ca3c3277be23
7
- data.tar.gz: 1d2ab4cb0c942e9818bbefaac4fa518afde793f7648cc64350d2c4c45201745e2e00be47ecc1942d093ce8a1a0459fa30060f46680bc77c789044b0f56a4e89b
6
+ metadata.gz: d2ade6a5c9b26e1d9d1bc1522dc10159f78943b5da249effa1405dccf5c3de5078f74a08c27657eef63d51967bb04eb8486d933e9d817664a25c33f3740f2280
7
+ data.tar.gz: 40dc9f0b3b1d210ddcec9d7ce5dd7014e2409d5602bb4b3c55314b5bf799ef29fab9fd0764931c2026242c91383a335b64c4f3243ea16d50997e59ef9c7820e3
data/CHANGELOG.md CHANGED
@@ -1,15 +1,79 @@
1
1
  # Changelog
2
2
 
3
- ## [v2.7.3](https://github.com/bensheldon/good_job/tree/v2.7.3) (2021-11-30)
3
+ ## [v2.9.0](https://github.com/bensheldon/good_job/tree/v2.9.0) (2022-01-09)
4
4
 
5
- [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.2...v2.7.3)
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.8.1...v2.9.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add JRuby / JDBC support for LISTEN [\#479](https://github.com/bensheldon/good_job/pull/479) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Remove demo CleanupJob in favor of using built-in cleanup intervals [\#478](https://github.com/bensheldon/good_job/pull/478) ([bensheldon](https://github.com/bensheldon))
14
+
15
+ ## [v2.8.1](https://github.com/bensheldon/good_job/tree/v2.8.1) (2022-01-03)
16
+
17
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.8.0...v2.8.1)
18
+
19
+ **Implemented enhancements:**
20
+
21
+ - Add indexes to `good_jobs.finished_at` and have `GoodJob.cleanup_preserved_jobs` delete all executions for a given job [\#477](https://github.com/bensheldon/good_job/pull/477) ([bensheldon](https://github.com/bensheldon))
22
+
23
+ **Closed issues:**
24
+
25
+ - finished\_at should be indexed and clean up should clean up all of a job's executions [\#476](https://github.com/bensheldon/good_job/issues/476)
26
+
27
+ **Merged pull requests:**
28
+
29
+ - Update development Ruby \(2.7.5\) and Rails \(6.1.4.4\) versions [\#475](https://github.com/bensheldon/good_job/pull/475) ([bensheldon](https://github.com/bensheldon))
30
+ - Clean up server integration tests [\#474](https://github.com/bensheldon/good_job/pull/474) ([bensheldon](https://github.com/bensheldon))
31
+
32
+ ## [v2.8.0](https://github.com/bensheldon/good_job/tree/v2.8.0) (2021-12-31)
33
+
34
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.4...v2.8.0)
35
+
36
+ **Implemented enhancements:**
37
+
38
+ - GoodJob should automatically clean up after itself and delete old job records [\#412](https://github.com/bensheldon/good_job/issues/412)
39
+ - Track processes in the database and on the Dashboard [\#472](https://github.com/bensheldon/good_job/pull/472) ([bensheldon](https://github.com/bensheldon))
40
+ - Allow Scheduler to automatically clean up preserved jobs every N jobs or seconds [\#465](https://github.com/bensheldon/good_job/pull/465) ([bensheldon](https://github.com/bensheldon))
41
+
42
+ **Closed issues:**
43
+
44
+ - Is there a way to show how many worker/process is running currently [\#471](https://github.com/bensheldon/good_job/issues/471)
45
+ - Jobs stuck in the unfinished state [\#448](https://github.com/bensheldon/good_job/issues/448)
46
+
47
+ **Merged pull requests:**
48
+
49
+ - Doublequote Ruby 3.0 in testing matrix [\#473](https://github.com/bensheldon/good_job/pull/473) ([bensheldon](https://github.com/bensheldon))
50
+ - Have demo CleanupJob use GoodJob.cleanup\_preserved\_jobs [\#470](https://github.com/bensheldon/good_job/pull/470) ([bensheldon](https://github.com/bensheldon))
51
+ - Test with Rails 7.0.0 [\#469](https://github.com/bensheldon/good_job/pull/469) ([aried3r](https://github.com/aried3r))
52
+
53
+ ## [v2.7.4](https://github.com/bensheldon/good_job/tree/v2.7.4) (2021-12-16)
54
+
55
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.3...v2.7.4)
6
56
 
7
57
  **Fixed bugs:**
8
58
 
9
- - Logger error on 2.7.2 [\#463](https://github.com/bensheldon/good_job/issues/463)
59
+ - Add nonce: true to javascript\_include\_tag in dashboard [\#468](https://github.com/bensheldon/good_job/pull/468) ([bouk](https://github.com/bouk))
60
+
61
+ **Closed issues:**
62
+
63
+ - Add nonce: true to engine views [\#467](https://github.com/bensheldon/good_job/issues/467)
64
+ - Updating good\_job breaks my Rails 7 alpha 2 local development [\#462](https://github.com/bensheldon/good_job/issues/462)
10
65
 
11
66
  **Merged pull requests:**
12
67
 
68
+ - Update appraisal for Rails 7.0.0.rc1 [\#466](https://github.com/bensheldon/good_job/pull/466) ([bensheldon](https://github.com/bensheldon))
69
+
70
+ ## [v2.7.3](https://github.com/bensheldon/good_job/tree/v2.7.3) (2021-11-30)
71
+
72
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.2...v2.7.3)
73
+
74
+ **Fixed bugs:**
75
+
76
+ - Logger error on 2.7.2 [\#463](https://github.com/bensheldon/good_job/issues/463)
13
77
  - Fix Railtie configuration assignment when Rails configuration is a Hash, not an OrderedOptions [\#464](https://github.com/bensheldon/good_job/pull/464) ([bensheldon](https://github.com/bensheldon))
14
78
 
15
79
  ## [v2.7.2](https://github.com/bensheldon/good_job/tree/v2.7.2) (2021-11-29)
data/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/good_job.svg)](https://rubygems.org/gems/good_job)
4
4
  [![Test Status](https://github.com/bensheldon/good_job/workflows/Test/badge.svg)](https://github.com/bensheldon/good_job/actions)
5
+ [![Ruby Toolbox](https://img.shields.io/badge/dynamic/json?color=blue&label=Ruby%20Toolbox&query=%24.projects%5B0%5D.score&url=https%3A%2F%2Fwww.ruby-toolbox.com%2Fapi%2Fprojects%2Fcompare%2Fgood_job&logo=data:image/svg+xml;base64,PHN2ZyBhcmlhLWhpZGRlbj0idHJ1ZSIgZm9jdXNhYmxlPSJmYWxzZSIgZGF0YS1wcmVmaXg9ImZhcyIgZGF0YS1pY29uPSJmbGFzayIgY2xhc3M9InN2Zy1pbmxpbmUtLWZhIGZhLWZsYXNrIGZhLXctMTQiIHJvbGU9ImltZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDQ4IDUxMiI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik00MzcuMiA0MDMuNUwzMjAgMjE1VjY0aDhjMTMuMyAwIDI0LTEwLjcgMjQtMjRWMjRjMC0xMy4zLTEwLjctMjQtMjQtMjRIMTIwYy0xMy4zIDAtMjQgMTAuNy0yNCAyNHYxNmMwIDEzLjMgMTAuNyAyNCAyNCAyNGg4djE1MUwxMC44IDQwMy41Qy0xOC41IDQ1MC42IDE1LjMgNTEyIDcwLjkgNTEyaDMwNi4yYzU1LjcgMCA4OS40LTYxLjUgNjAuMS0xMDguNXpNMTM3LjkgMzIwbDQ4LjItNzcuNmMzLjctNS4yIDUuOC0xMS42IDUuOC0xOC40VjY0aDY0djE2MGMwIDYuOSAyLjIgMTMuMiA1LjggMTguNGw0OC4yIDc3LjZoLTE3MnoiPjwvcGF0aD48L3N2Zz4=)](https://www.ruby-toolbox.com/projects/good_job)
5
6
 
6
7
  GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
7
8
 
@@ -270,6 +271,9 @@ Available configuration options are:
270
271
  - `shutdown_timeout` (float) number of seconds to wait for jobs to finish when shutting down before stopping the thread. Defaults to forever: `-1`. You can also set this with the environment variable `GOOD_JOB_SHUTDOWN_TIMEOUT`.
271
272
  - `enable_cron` (boolean) whether to run cron process. Defaults to `false`. You can also set this with the environment variable `GOOD_JOB_ENABLE_CRON`.
272
273
  - `cron` (hash) cron configuration. Defaults to `{}`. You can also set this as a JSON string with the environment variable `GOOD_JOB_CRON`
274
+ - `cleanup_preserved_jobs_before_seconds_ago` (integer) number of seconds to preserve jobs when using the `$ good_job cleanup_preserved_jobs` CLI command or calling `GoodJob.cleanup_preserved_jobs`. Defaults to `86400` (1 day). Can also be set with the environment variable `GOOD_JOB_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO`. _This configuration is only used when {GoodJob.preserve_job_records} is `true`._
275
+ - `cleanup_interval_jobs` (integer) Number of jobs a Scheduler will execute before cleaning up preserved jobs. Defaults to `nil`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_JOBS`.
276
+ - `cleanup_interval_seconds` (integer) Number of seconds a Scheduler will wait before cleaning up preserved jobs. Defaults to `nil`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_SECONDS`.
273
277
  - `logger` ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger` (Default: `Rails.logger`).
274
278
  - `preserve_job_records` (boolean) keeps job records in your database even after jobs are completed. (Default: `false`)
275
279
  - `retry_on_unhandled_error` (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `true`)
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ class ProcessesController < GoodJob::BaseController
4
+ def index
5
+ @processes = GoodJob::Process.active.order(created_at: :desc) if GoodJob::Process.migrated?
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,40 @@
1
+ <% if !GoodJob::Process.migrated? %>
2
+ <div class="card my-3">
3
+ <div class="card-body">
4
+ <p class="card-text">
5
+ <em>Feature unavailable because of pending database migration.</em>
6
+ </p>
7
+ </div>
8
+ </div>
9
+ <% elsif @processes.present? %>
10
+ <div class="card my-3">
11
+ <div class="table-responsive">
12
+ <table class="table card-table table-bordered table-hover table-sm mb-0">
13
+ <thead>
14
+ <tr>
15
+ <th>Process UUID</th>
16
+ <th>Created At</th></th>
17
+ <th>State</th>
18
+ </tr>
19
+ </thead>
20
+ <tbody>
21
+ <% @processes.each do |process| %>
22
+ <tr class="<%= dom_class(process) %>" id="<%= dom_id(process) %>">
23
+ <td><%= process.id %></td>
24
+ <td><%= relative_time(process.created_at) %></td>
25
+ <td><%= tag.pre JSON.pretty_generate(process.state) %></td>
26
+ </tr>
27
+ <% end %>
28
+ </tbody>
29
+ </table>
30
+ </div>
31
+ </div>
32
+ <% else %>
33
+ <div class="card my-3">
34
+ <div class="card-body">
35
+ <p class="card-text">
36
+ <em>No GoodJob processes found.</em>
37
+ </p>
38
+ </div>
39
+ </div>
40
+ <% end %>
@@ -8,11 +8,11 @@
8
8
  <%= stylesheet_link_tag bootstrap_path(format: :css, v: GoodJob::VERSION) %>
9
9
  <%= stylesheet_link_tag style_path(format: :css, v: GoodJob::VERSION) %>
10
10
 
11
- <%= javascript_include_tag bootstrap_path(format: :js, v: GoodJob::VERSION) %>
12
- <%= javascript_include_tag chartjs_path(format: :js, v: GoodJob::VERSION) %>
13
- <%= javascript_include_tag scripts_path(format: :js, v: GoodJob::VERSION) %>
11
+ <%= javascript_include_tag bootstrap_path(format: :js, v: GoodJob::VERSION), nonce: true %>
12
+ <%= javascript_include_tag chartjs_path(format: :js, v: GoodJob::VERSION), nonce: true %>
13
+ <%= javascript_include_tag scripts_path(format: :js, v: GoodJob::VERSION), nonce: true %>
14
14
 
15
- <%= javascript_include_tag rails_ujs_path(format: :js, v: GoodJob::VERSION) %>
15
+ <%= javascript_include_tag rails_ujs_path(format: :js, v: GoodJob::VERSION), nonce: true %>
16
16
  </head>
17
17
  <body>
18
18
  <nav class="navbar navbar-expand-lg navbar-light bg-light">
@@ -33,23 +33,14 @@
33
33
  <li class="nav-item">
34
34
  <%= link_to "Cron Schedules", cron_entries_path, class: ["nav-link", ("active" if current_page?(cron_entries_path))] %>
35
35
  </li>
36
+ <li class="nav-item">
37
+ <%= link_to "Processes", processes_path, class: ["nav-link", ("active" if current_page?(processes_path))] %>
38
+ </li>
36
39
  <li class="nav-item">
37
40
  <div class="nav-link">
38
41
  <span class="badge bg-secondary">More views coming soon</span>
39
42
  </div>
40
43
  </li>
41
-
42
- <!-- Coming Soon
43
- <li class="nav-item">
44
- <%= link_to "Upcoming Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
45
- </li>
46
- <li class="nav-item">
47
- <%= link_to "Finished Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
48
- </li>
49
- <li class="nav-item">
50
- <%= link_to "Errored Jobs", 'todo', class: ["nav-link", ("active" if current_page?('todo'))] %>
51
- </li>
52
- -->
53
44
  </ul>
54
45
  <div class="text-muted" title="Now is <%= Time.current %>">Times are displayed in <%= Time.current.zone %> timezone</div>
55
46
  </div>
@@ -2,11 +2,7 @@
2
2
  GoodJob::Engine.routes.draw do
3
3
  root to: 'executions#index'
4
4
 
5
- resources :cron_entries, only: %i[index show] do
6
- member do
7
- post :enqueue
8
- end
9
- end
5
+ resources :executions, only: %i[destroy]
10
6
 
11
7
  resources :jobs, only: %i[index show] do
12
8
  member do
@@ -15,7 +11,14 @@ GoodJob::Engine.routes.draw do
15
11
  put :retry
16
12
  end
17
13
  end
18
- resources :executions, only: %i[destroy]
14
+
15
+ resources :cron_entries, only: %i[index show] do
16
+ member do
17
+ post :enqueue
18
+ end
19
+ end
20
+
21
+ resources :processes, only: %i[index]
19
22
 
20
23
  scope controller: :assets do
21
24
  constraints(format: :css) do
@@ -21,11 +21,18 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
21
21
  t.timestamp :cron_at
22
22
  end
23
23
 
24
+ create_table :good_job_processes, id: :uuid do |t|
25
+ t.timestamps
26
+ t.jsonb :state
27
+ end
28
+
24
29
  add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: "index_good_jobs_on_scheduled_at"
25
30
  add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
26
31
  add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
27
32
  add_index :good_jobs, :concurrency_key, where: "(finished_at IS NULL)", name: :index_good_jobs_on_concurrency_key_when_unfinished
28
33
  add_index :good_jobs, [:cron_key, :created_at], name: :index_good_jobs_on_cron_key_and_created_at
29
34
  add_index :good_jobs, [:cron_key, :cron_at], name: :index_good_jobs_on_cron_key_and_cron_at, unique: true
35
+ add_index :good_jobs, [:active_job_id], name: :index_good_jobs_on_active_job_id
36
+ add_index :good_jobs, [:finished_at], where: "retried_good_job_id IS NULL AND finished_at IS NOT NULL", name: :index_good_jobs_jobs_on_finished_at
30
37
  end
31
38
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ class CreateGoodJobProcesses < ActiveRecord::Migration<%= migration_version %>
3
+ def change
4
+ reversible do |dir|
5
+ dir.up do
6
+ # Ensure this incremental update migration is idempotent
7
+ # with monolithic install migration.
8
+ return if connection.table_exists?(:good_job_processes)
9
+ end
10
+ end
11
+
12
+ create_table :good_job_processes, id: :uuid do |t|
13
+ t.timestamps
14
+ t.jsonb :state
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ class IndexGoodJobJobsOnFinishedAt < ActiveRecord::Migration<%= migration_version %>
3
+ disable_ddl_transaction!
4
+
5
+ def change
6
+ reversible do |dir|
7
+ dir.up do
8
+ # Ensure this incremental update migration is idempotent
9
+ # with monolithic install migration.
10
+ return if connection.index_name_exists?(:good_jobs, :index_good_jobs_on_active_job_id)
11
+ end
12
+ end
13
+
14
+ add_index :good_jobs,
15
+ [:active_job_id],
16
+ name: :index_good_jobs_on_active_job_id,
17
+ algorithm: :concurrently
18
+
19
+ add_index :good_jobs,
20
+ [:finished_at],
21
+ where: "retried_good_job_id IS NULL AND finished_at IS NOT NULL",
22
+ name: :index_good_jobs_jobs_on_finished_at,
23
+ algorithm: :concurrently
24
+ end
25
+ end
@@ -4,10 +4,7 @@ module GoodJob
4
4
  # There is not a table in the database whose discrete rows represents "Jobs".
5
5
  # The +good_jobs+ table is a table of individual {GoodJob::Execution}s that share the same +active_job_id+.
6
6
  # A single row from the +good_jobs+ table of executions is fetched to represent an ActiveJobJob
7
- # Parent class can be configured with +GoodJob.active_record_parent_class+.
8
- # @!parse
9
- # class ActiveJob < ActiveRecord::Base; end
10
- class ActiveJobJob < Object.const_get(GoodJob.active_record_parent_class)
7
+ class ActiveJobJob < BaseRecord
11
8
  include Filterable
12
9
  include Lockable
13
10
 
@@ -22,7 +19,7 @@ module GoodJob
22
19
  self.primary_key = 'active_job_id'
23
20
  self.advisory_lockable_column = 'active_job_id'
24
21
 
25
- has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id'
22
+ has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', inverse_of: :job
26
23
 
27
24
  # Only the most-recent unretried execution represents a "Job"
28
25
  default_scope { where(retried_good_job_id: nil) }
@@ -30,11 +27,17 @@ module GoodJob
30
27
  # Get Jobs with given class name
31
28
  # @!method job_class
32
29
  # @!scope class
33
- # @param string [String]
34
- # Execution class name
30
+ # @param string [String] Execution class name
35
31
  # @return [ActiveRecord::Relation]
36
32
  scope :job_class, ->(job_class) { where("serialized_params->>'job_class' = ?", job_class) }
37
33
 
34
+ # Get Jobs finished before the given timestamp.
35
+ # @!method finished_before(timestamp)
36
+ # @!scope class
37
+ # @param timestamp (DateTime, Time)
38
+ # @return [ActiveRecord::Relation]
39
+ scope :finished_before, ->(timestamp) { where(arel_table['finished_at'].lteq(timestamp)) }
40
+
38
41
  # First execution will run in the future
39
42
  scope :scheduled, -> { where(finished_at: nil).where('COALESCE(scheduled_at, created_at) > ?', DateTime.current).where("(serialized_params->>'executions')::integer < 2") }
40
43
  # Execution errored, will run in the future
@@ -121,7 +121,7 @@ module GoodJob
121
121
  end
122
122
 
123
123
  # Start async executors
124
- # @return void
124
+ # @return [void]
125
125
  def start_async
126
126
  return unless execute_async?
127
127
 
@@ -149,7 +149,7 @@ module GoodJob
149
149
  def in_server_process?
150
150
  return @_in_server_process if defined? @_in_server_process
151
151
 
152
- @_in_server_process = Rails.const_defined?('Server') ||
152
+ @_in_server_process = Rails.const_defined?(:Server) ||
153
153
  caller.grep(%r{config.ru}).any? || # EXAMPLE: config.ru:3:in `block in <main>' OR config.ru:3:in `new_from_string'
154
154
  caller.grep(%{/rack/handler/}).any? || # EXAMPLE: iodine-0.7.44/lib/rack/handler/iodine.rb:13:in `start'
155
155
  (Concurrent.on_jruby? && caller.grep(%r{jruby/rack/rails_booter}).any?) # EXAMPLE: uri:classloader:/jruby/rack/rails_booter.rb:83:in `load_environment'
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob # :nodoc:
3
+ # Extends an ActiveRecord odel to override the connection and use
4
+ # an explicit connection that has been removed from the pool.
5
+ module AssignableConnection
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ thread_cattr_accessor :_connection
10
+ end
11
+
12
+ class_methods do
13
+ # Assigns a connection to the model.
14
+ # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter]
15
+ # @return [void]
16
+ def connection=(conn)
17
+ self._connection = conn
18
+ end
19
+
20
+ # Overrides the existing connection method to use the assigned connection
21
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
22
+ def connection
23
+ _connection || super
24
+ end
25
+
26
+ # Block interface to assign the connection, yield, then unassign the connection.
27
+ # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter]
28
+ # @return [void]
29
+ def with_connection(conn)
30
+ original_conn = _connection
31
+ self.connection = conn
32
+ yield
33
+ ensure
34
+ self._connection = original_conn
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ # Base ActiveRecord class that all GoodJob models inherit from.
4
+ # Parent class can be configured with +GoodJob.active_record_parent_class+.
5
+ # @!parse
6
+ # class BaseRecord < ActiveRecord::Base; end
7
+ class BaseRecord < Object.const_get(GoodJob.active_record_parent_class)
8
+ self.abstract_class = true
9
+
10
+ def self.migration_pending_warning!
11
+ ActiveSupport::Deprecation.warn(<<~DEPRECATION)
12
+ GoodJob has pending database migrations. To create the migration files, run:
13
+ rails generate good_job:update
14
+ To apply the migration files, run:
15
+ rails db:migrate
16
+ DEPRECATION
17
+ nil
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob # :nodoc:
3
+ # Tracks thresholds for cleaning up old jobs.
4
+ class CleanupTracker
5
+ attr_accessor :cleanup_interval_seconds,
6
+ :cleanup_interval_jobs,
7
+ :job_count,
8
+ :last_at
9
+
10
+ def initialize(cleanup_interval_seconds: nil, cleanup_interval_jobs: nil)
11
+ self.cleanup_interval_seconds = cleanup_interval_seconds
12
+ self.cleanup_interval_jobs = cleanup_interval_jobs
13
+
14
+ reset
15
+ end
16
+
17
+ # Increments job count.
18
+ # @return [void]
19
+ def increment
20
+ self.job_count += 1
21
+ end
22
+
23
+ # Whether a cleanup should be run.
24
+ # @return [Boolean]
25
+ def cleanup?
26
+ (cleanup_interval_jobs && job_count > cleanup_interval_jobs) ||
27
+ (cleanup_interval_seconds && last_at < Time.current - cleanup_interval_seconds) ||
28
+ false
29
+ end
30
+
31
+ # Resets the counters.
32
+ # @return [void]
33
+ def reset
34
+ self.job_count = 0
35
+ self.last_at = Time.current
36
+ end
37
+ end
38
+ end
@@ -18,6 +18,10 @@ module GoodJob
18
18
  DEFAULT_MAX_CACHE = 10000
19
19
  # Default number of seconds to preserve jobs for {CLI#cleanup_preserved_jobs} and {GoodJob.cleanup_preserved_jobs}
20
20
  DEFAULT_CLEANUP_PRESERVED_JOBS_BEFORE_SECONDS_AGO = 24 * 60 * 60
21
+ # Default number of jobs to execute between preserved job cleanup runs
22
+ DEFAULT_CLEANUP_INTERVAL_JOBS = nil
23
+ # Default number of seconds to wait between preserved job cleanup runs
24
+ DEFAULT_CLEANUP_INTERVAL_SECONDS = nil
21
25
  # Default to always wait for jobs to finish for {Adapter#shutdown}
22
26
  DEFAULT_SHUTDOWN_TIMEOUT = -1
23
27
  # Default to not running cron
@@ -179,6 +183,28 @@ module GoodJob
179
183
  ).to_i
180
184
  end
181
185
 
186
+ # Number of jobs a {Scheduler} will execute before cleaning up preserved jobs.
187
+ # @return [Integer, nil]
188
+ def cleanup_interval_jobs
189
+ value = (
190
+ rails_config[:cleanup_interval_jobs] ||
191
+ env['GOOD_JOB_CLEANUP_INTERVAL_JOBS'] ||
192
+ DEFAULT_CLEANUP_INTERVAL_JOBS
193
+ )
194
+ value.present? ? value.to_i : nil
195
+ end
196
+
197
+ # Number of seconds a {Scheduler} will wait before cleaning up preserved jobs.
198
+ # @return [Integer, nil]
199
+ def cleanup_interval_seconds
200
+ value = (
201
+ rails_config[:cleanup_interval_seconds] ||
202
+ env['GOOD_JOB_CLEANUP_INTERVAL_SECONDS'] ||
203
+ DEFAULT_CLEANUP_INTERVAL_SECONDS
204
+ )
205
+ value.present? ? value.to_i : nil
206
+ end
207
+
182
208
  # Tests whether to daemonize the process.
183
209
  # @return [Boolean]
184
210
  def daemonize?
@@ -68,7 +68,7 @@ module GoodJob
68
68
 
69
69
  # @return [Integer] Current process ID
70
70
  def self.process_id
71
- Process.pid
71
+ ::Process.pid
72
72
  end
73
73
 
74
74
  # @return [String] Current thread name
@@ -17,7 +17,7 @@ module GoodJob
17
17
  # @return [void]
18
18
  def daemonize
19
19
  check_pid
20
- Process.daemon
20
+ ::Process.daemon
21
21
  write_pid
22
22
  end
23
23
 
@@ -25,7 +25,7 @@ module GoodJob
25
25
 
26
26
  # @return [void]
27
27
  def write_pid
28
- File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY) { |f| f.write(Process.pid.to_s) }
28
+ File.open(pidfile, ::File::CREAT | ::File::EXCL | ::File::WRONLY) { |f| f.write(::Process.pid.to_s) }
29
29
  at_exit { File.delete(pidfile) if File.exist?(pidfile) }
30
30
  rescue Errno::EEXIST
31
31
  check_pid
@@ -55,7 +55,7 @@ module GoodJob
55
55
  pid = ::File.read(pidfile).to_i
56
56
  return :dead if pid.zero?
57
57
 
58
- Process.kill(0, pid) # check process status
58
+ ::Process.kill(0, pid) # check process status
59
59
  :running
60
60
  rescue Errno::ESRCH
61
61
  :dead
@@ -1,10 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # ActiveRecord model that represents an +ActiveJob+ job.
4
- # Parent class can be configured with +GoodJob.active_record_parent_class+.
5
- # @!parse
6
- # class Execution < ActiveRecord::Base; end
7
- class Execution < Object.const_get(GoodJob.active_record_parent_class)
4
+ class Execution < BaseRecord
8
5
  include Lockable
9
6
  include Filterable
10
7
 
@@ -54,15 +51,7 @@ module GoodJob
54
51
  end
55
52
  end
56
53
 
57
- def self._migration_pending_warning
58
- ActiveSupport::Deprecation.warn(<<~DEPRECATION)
59
- GoodJob has pending database migrations. To create the migration files, run:
60
- rails generate good_job:update
61
- To apply the migration files, run:
62
- rails db:migrate
63
- DEPRECATION
64
- nil
65
- end
54
+ belongs_to :job, class_name: 'GoodJob::ActiveJobJob', foreign_key: 'active_job_id', primary_key: 'active_job_id', optional: true, inverse_of: :executions
66
55
 
67
56
  # Get Jobs with given ActiveJob ID
68
57
  # @!method active_job_id
@@ -224,7 +213,7 @@ module GoodJob
224
213
  if @cron_at_index
225
214
  execution_args[:cron_at] = CurrentThread.cron_at
226
215
  else
227
- _migration_pending_warning
216
+ migration_pending_warning!
228
217
  end
229
218
  elsif CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
230
219
  execution_args[:cron_key] = CurrentThread.execution.cron_key
@@ -57,6 +57,12 @@ module GoodJob
57
57
  job_query.next_scheduled_at(after: after, limit: limit, now_limit: now_limit)
58
58
  end
59
59
 
60
+ # Delete expired preserved jobs
61
+ # @return [void]
62
+ def cleanup
63
+ GoodJob.cleanup_preserved_jobs
64
+ end
65
+
60
66
  private
61
67
 
62
68
  attr_reader :queue_string
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob # :nodoc:
4
+ class Notifier # :nodoc:
5
+ # Extends the Notifier to register the process in the database.
6
+ module ProcessRegistration
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ set_callback :listen, :after, :register_process
11
+ set_callback :unlisten, :after, :deregister_process
12
+ end
13
+
14
+ # Registers the current process.
15
+ def register_process
16
+ GoodJob::Process.with_connection(connection) do
17
+ next unless Process.migrated?
18
+
19
+ GoodJob::Process.cleanup
20
+ @process = GoodJob::Process.register
21
+ end
22
+ end
23
+
24
+ # Deregisters the current process.
25
+ def deregister_process
26
+ GoodJob::Process.with_connection(connection) do
27
+ next unless Process.migrated?
28
+
29
+ @process&.deregister
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'active_support/core_ext/module/attribute_accessors_per_thread'
2
3
  require 'concurrent/atomic/atomic_boolean'
3
4
 
4
5
  module GoodJob # :nodoc:
@@ -10,8 +11,10 @@ module GoodJob # :nodoc:
10
11
  # When a message is received, the notifier passes the message to each of its recipients.
11
12
  #
12
13
  class Notifier
13
- # Raised if the Database adapter does not implement LISTEN.
14
- AdapterCannotListenError = Class.new(StandardError)
14
+ include ActiveSupport::Callbacks
15
+ define_callbacks :listen, :unlisten
16
+
17
+ include Notifier::ProcessRegistration
15
18
 
16
19
  # Default Postgres channel for LISTEN/NOTIFY
17
20
  CHANNEL = 'good_job'
@@ -43,6 +46,12 @@ module GoodJob # :nodoc:
43
46
  # @return [Array<GoodJob::Notifier>, nil]
44
47
  cattr_reader :instances, default: [], instance_reader: false
45
48
 
49
+ # @!attribute [rw] connection
50
+ # @!scope class
51
+ # ActiveRecord Connection that has been established for the Notifier.
52
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter, nil]
53
+ thread_cattr_accessor :connection
54
+
46
55
  # Send a message via Postgres NOTIFY
47
56
  # @param message [#to_json]
48
57
  def self.notify(message)
@@ -117,8 +126,6 @@ module GoodJob # :nodoc:
117
126
  # @!visibility private
118
127
  # @return [void]
119
128
  def listen_observer(_time, _result, thread_error)
120
- return if thread_error.is_a? AdapterCannotListenError
121
-
122
129
  if thread_error
123
130
  GoodJob._on_thread_error(thread_error)
124
131
  ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
@@ -146,30 +153,36 @@ module GoodJob # :nodoc:
146
153
 
147
154
  def listen(delay: 0)
148
155
  future = Concurrent::ScheduledTask.new(delay, args: [@recipients, executor, @listening], executor: @executor) do |thr_recipients, thr_executor, thr_listening|
149
- with_listen_connection do |conn|
150
- ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
151
- conn.async_exec("LISTEN #{CHANNEL}").clear
152
- end
156
+ with_connection do
157
+ begin
158
+ run_callbacks :listen do
159
+ ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
160
+ connection.execute("LISTEN #{CHANNEL}")
161
+ end
162
+ thr_listening.make_true
163
+ end
153
164
 
154
- ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
155
- thr_listening.make_true
156
- while thr_executor.running?
157
- conn.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
158
- next unless channel == CHANNEL
159
-
160
- ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
161
- parsed_payload = JSON.parse(payload, symbolize_names: true)
162
- thr_recipients.each do |recipient|
163
- target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
164
- target.send(method_name, parsed_payload)
165
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
166
+ while thr_executor.running?
167
+ wait_for_notify do |channel, payload|
168
+ next unless channel == CHANNEL
169
+
170
+ ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
171
+ parsed_payload = JSON.parse(payload, symbolize_names: true)
172
+ thr_recipients.each do |recipient|
173
+ target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
174
+ target.send(method_name, parsed_payload)
175
+ end
165
176
  end
166
177
  end
167
178
  end
168
179
  end
169
180
  ensure
170
- thr_listening.make_false
171
- ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
172
- conn.async_exec("UNLISTEN *").clear
181
+ run_callbacks :unlisten do
182
+ thr_listening.make_false
183
+ ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
184
+ connection.execute("UNLISTEN *")
185
+ end
173
186
  end
174
187
  end
175
188
  end
@@ -178,17 +191,36 @@ module GoodJob # :nodoc:
178
191
  future.execute
179
192
  end
180
193
 
181
- def with_listen_connection
182
- ar_conn = Execution.connection_pool.checkout.tap do |conn|
194
+ def with_connection
195
+ self.connection = Execution.connection_pool.checkout.tap do |conn|
183
196
  Execution.connection_pool.remove(conn)
184
197
  end
185
- pg_conn = ar_conn.raw_connection
186
- raise AdapterCannotListenError unless pg_conn.respond_to? :wait_for_notify
198
+ connection.execute("SET application_name = #{connection.quote(self.class.name)}")
187
199
 
188
- pg_conn.async_exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}").clear
189
- yield pg_conn
200
+ yield
190
201
  ensure
191
- ar_conn&.disconnect!
202
+ connection&.disconnect!
203
+ self.connection = nil
204
+ end
205
+
206
+ def wait_for_notify
207
+ raw_connection = connection.raw_connection
208
+ if raw_connection.respond_to?(:wait_for_notify)
209
+ raw_connection.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
210
+ yield(channel, payload)
211
+ end
212
+ elsif raw_connection.respond_to?(:jdbc_connection)
213
+ raw_connection.execute_query("SELECT 1")
214
+ notifications = raw_connection.jdbc_connection.getNotifications
215
+ Array(notifications).each do |notification|
216
+ channel = notification.getName
217
+ payload = notification.getParameter
218
+ yield(channel, payload)
219
+ end
220
+ sleep WAIT_INTERVAL
221
+ else
222
+ sleep WAIT_INTERVAL
223
+ end
192
224
  end
193
225
  end
194
226
  end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ require 'socket'
3
+
4
+ module GoodJob # :nodoc:
5
+ # ActiveRecord model that represents an GoodJob process (either async or CLI).
6
+ class Process < BaseRecord
7
+ include AssignableConnection
8
+ include Lockable
9
+
10
+ self.table_name = 'good_job_processes'
11
+
12
+ cattr_reader :mutex, default: Mutex.new
13
+ cattr_accessor :_current_id, default: nil
14
+ cattr_accessor :_pid, default: nil
15
+
16
+ # Processes that are active and locked.
17
+ # @!method active
18
+ # @!scope class
19
+ # @return [ActiveRecord::Relation]
20
+ scope :active, -> { advisory_locked }
21
+
22
+ # Processes that are inactive and unlocked (e.g. SIGKILLed)
23
+ # @!method active
24
+ # @!scope class
25
+ # @return [ActiveRecord::Relation]
26
+ scope :inactive, -> { advisory_unlocked }
27
+
28
+ # Whether the +good_job_processes+ table exsists.
29
+ # @return [Boolean]
30
+ def self.migrated?
31
+ return true if connection.table_exists?(table_name)
32
+
33
+ migration_pending_warning!
34
+ false
35
+ end
36
+
37
+ # UUID that is unique to the current process and changes when forked.
38
+ # @return [String]
39
+ def self.current_id
40
+ mutex.synchronize do
41
+ if _current_id.nil? || _pid != ::Process.pid
42
+ self._current_id = SecureRandom.uuid
43
+ self._pid = ::Process.pid
44
+ end
45
+ _current_id
46
+ end
47
+ end
48
+
49
+ # Hash representing metadata about the current process.
50
+ # @return [Hash]
51
+ def self.current_state
52
+ {
53
+ id: current_id,
54
+ hostname: Socket.gethostname,
55
+ pid: ::Process.pid,
56
+ proctitle: $PROGRAM_NAME,
57
+ schedulers: GoodJob::Scheduler.instances.map(&:name),
58
+ }
59
+ end
60
+
61
+ # Deletes all inactive process records.
62
+ def self.cleanup
63
+ inactive.delete_all
64
+ end
65
+
66
+ # Registers the current process in the database
67
+ # @return [GoodJob::Process]
68
+ def self.register
69
+ create(id: current_id, state: current_state, create_with_advisory_lock: true)
70
+ rescue ActiveRecord::RecordNotUnique
71
+ nil
72
+ end
73
+
74
+ # Unregisters the instance.
75
+ def deregister
76
+ return unless owns_advisory_lock?
77
+
78
+ destroy!
79
+ advisory_unlock
80
+ end
81
+ end
82
+ end
@@ -48,7 +48,9 @@ module GoodJob # :nodoc:
48
48
  job_performer,
49
49
  max_threads: max_threads,
50
50
  max_cache: configuration.max_cache,
51
- warm_cache_on_initialize: warm_cache_on_initialize
51
+ warm_cache_on_initialize: warm_cache_on_initialize,
52
+ cleanup_interval_seconds: configuration.cleanup_interval_seconds,
53
+ cleanup_interval_jobs: configuration.cleanup_interval_jobs
52
54
  )
53
55
  end
54
56
 
@@ -59,11 +61,17 @@ module GoodJob # :nodoc:
59
61
  end
60
62
  end
61
63
 
64
+ # Human readable name of the scheduler that includes configuration values.
65
+ # @return [String]
66
+ attr_reader :name
67
+
62
68
  # @param performer [GoodJob::JobPerformer]
63
69
  # @param max_threads [Numeric, nil] number of seconds between polls for jobs
64
70
  # @param max_cache [Numeric, nil] maximum number of scheduled jobs to cache in memory
65
71
  # @param warm_cache_on_initialize [Boolean] whether to warm the cache immediately, or manually by calling +warm_cache+
66
- def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize: false)
72
+ # @param cleanup_interval_seconds [Numeric, nil] number of seconds between cleaning up job records
73
+ # @param cleanup_interval_jobs [Numeric, nil] number of executed jobs between cleaning up job records
74
+ def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize: false, cleanup_interval_seconds: nil, cleanup_interval_jobs: nil)
67
75
  raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
68
76
 
69
77
  self.class.instances << self
@@ -76,8 +84,10 @@ module GoodJob # :nodoc:
76
84
  @executor_options[:max_threads] = max_threads
77
85
  @executor_options[:max_queue] = max_threads
78
86
  end
79
- @executor_options[:name] = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@executor_options[:max_threads]})"
87
+ @name = "GoodJob::Scheduler(queues=#{@performer.name} max_threads=#{@executor_options[:max_threads]})"
88
+ @executor_options[:name] = name
80
89
 
90
+ @cleanup_tracker = CleanupTracker.new(cleanup_interval_seconds: cleanup_interval_seconds, cleanup_interval_jobs: cleanup_interval_jobs)
81
91
  create_executor
82
92
  warm_cache if warm_cache_on_initialize
83
93
  end
@@ -172,7 +182,14 @@ module GoodJob # :nodoc:
172
182
  GoodJob._on_thread_error(error) if error
173
183
 
174
184
  instrument("finished_job_task", { result: output, error: thread_error, time: time })
175
- create_task if output
185
+ return unless output
186
+
187
+ @cleanup_tracker.increment
188
+ if @cleanup_tracker.cleanup?
189
+ cleanup
190
+ else
191
+ create_task
192
+ end
176
193
  end
177
194
 
178
195
  # Information about the Scheduler
@@ -210,7 +227,25 @@ module GoodJob # :nodoc:
210
227
  create_task # If cache-warming exhausts the threads, ensure there isn't an executable task remaining
211
228
  end
212
229
  future.add_observer(observer, :call)
230
+ future.execute
231
+ end
232
+
233
+ # Preload existing runnable and future-scheduled jobs
234
+ # @return [void]
235
+ def cleanup
236
+ @cleanup_tracker.reset
213
237
 
238
+ future = Concurrent::Future.new(args: [self, @performer], executor: executor) do |_thr_scheduler, thr_performer|
239
+ Rails.application.executor.wrap do
240
+ thr_performer.cleanup
241
+ end
242
+ end
243
+
244
+ observer = lambda do |_time, _output, thread_error|
245
+ GoodJob._on_thread_error(thread_error) if thread_error
246
+ create_task
247
+ end
248
+ future.add_observer(observer, :call)
214
249
  future.execute
215
250
  end
216
251
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '2.7.3'
4
+ VERSION = '2.9.0'
5
5
  end
data/lib/good_job.rb CHANGED
@@ -131,16 +131,18 @@ module GoodJob
131
131
  # analyze or inspect job performance.
132
132
  # If you are preserving job records this way, use this method regularly to
133
133
  # delete old records and preserve space in your database.
134
- # @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs olders than this will be deleted (default: +86400+).
134
+ # @params older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be deleted (default: +86400+).
135
135
  # @return [Integer] Number of jobs that were deleted.
136
136
  def self.cleanup_preserved_jobs(older_than: nil)
137
137
  older_than ||= GoodJob::Configuration.new({}).cleanup_preserved_jobs_before_seconds_ago
138
138
  timestamp = Time.current - older_than
139
139
 
140
140
  ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { older_than: older_than, timestamp: timestamp }) do |payload|
141
- deleted_records_count = GoodJob::Execution.finished(timestamp).delete_all
141
+ old_jobs = GoodJob::ActiveJobJob.where('finished_at <= ?', timestamp)
142
+ old_jobs_count = old_jobs.count
142
143
 
143
- payload[:deleted_records_count] = deleted_records_count
144
+ GoodJob::Execution.where(job: old_jobs).delete_all
145
+ payload[:deleted_records_count] = old_jobs_count
144
146
  end
145
147
  end
146
148
 
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: 2.7.3
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-11-30 00:00:00.000000000 Z
11
+ date: 2022-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -372,6 +372,7 @@ files:
372
372
  - engine/app/controllers/good_job/cron_entries_controller.rb
373
373
  - engine/app/controllers/good_job/executions_controller.rb
374
374
  - engine/app/controllers/good_job/jobs_controller.rb
375
+ - engine/app/controllers/good_job/processes_controller.rb
375
376
  - engine/app/filters/good_job/base_filter.rb
376
377
  - engine/app/filters/good_job/executions_filter.rb
377
378
  - engine/app/filters/good_job/jobs_filter.rb
@@ -383,6 +384,7 @@ files:
383
384
  - engine/app/views/good_job/jobs/_table.erb
384
385
  - engine/app/views/good_job/jobs/index.html.erb
385
386
  - engine/app/views/good_job/jobs/show.html.erb
387
+ - engine/app/views/good_job/processes/index.html.erb
386
388
  - engine/app/views/good_job/shared/_chart.erb
387
389
  - engine/app/views/good_job/shared/_filter.erb
388
390
  - engine/app/views/good_job/shared/icons/_arrow_clockwise.html.erb
@@ -402,12 +404,17 @@ files:
402
404
  - lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
403
405
  - lib/generators/good_job/templates/update/migrations/02_add_cron_at_to_good_jobs.rb.erb
404
406
  - lib/generators/good_job/templates/update/migrations/03_add_cron_key_cron_at_index_to_good_jobs.rb.erb
407
+ - lib/generators/good_job/templates/update/migrations/04_create_good_job_processes.rb.erb
408
+ - lib/generators/good_job/templates/update/migrations/04_index_good_job_jobs_on_finished_at.rb.erb
405
409
  - lib/generators/good_job/update_generator.rb
406
410
  - lib/good_job.rb
407
411
  - lib/good_job/active_job_extensions.rb
408
412
  - lib/good_job/active_job_extensions/concurrency.rb
409
413
  - lib/good_job/active_job_job.rb
410
414
  - lib/good_job/adapter.rb
415
+ - lib/good_job/assignable_connection.rb
416
+ - lib/good_job/base_record.rb
417
+ - lib/good_job/cleanup_tracker.rb
411
418
  - lib/good_job/cli.rb
412
419
  - lib/good_job/configuration.rb
413
420
  - lib/good_job/cron_entry.rb
@@ -423,8 +430,10 @@ files:
423
430
  - lib/good_job/log_subscriber.rb
424
431
  - lib/good_job/multi_scheduler.rb
425
432
  - lib/good_job/notifier.rb
433
+ - lib/good_job/notifier/process_registration.rb
426
434
  - lib/good_job/poller.rb
427
435
  - lib/good_job/probe_server.rb
436
+ - lib/good_job/process.rb
428
437
  - lib/good_job/railtie.rb
429
438
  - lib/good_job/scheduler.rb
430
439
  - lib/good_job/version.rb
@@ -437,6 +446,7 @@ metadata:
437
446
  documentation_uri: https://rdoc.info/github/bensheldon/good_job
438
447
  homepage_uri: https://github.com/bensheldon/good_job
439
448
  source_code_uri: https://github.com/bensheldon/good_job
449
+ rubygems_mfa_required: 'true'
440
450
  post_install_message:
441
451
  rdoc_options:
442
452
  - "--title"
@@ -460,7 +470,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
460
470
  - !ruby/object:Gem::Version
461
471
  version: '0'
462
472
  requirements: []
463
- rubygems_version: 3.2.30
473
+ rubygems_version: 3.1.6
464
474
  signing_key:
465
475
  specification_version: 4
466
476
  summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails