good_job 2.7.2 → 2.8.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4843101f6fce50527e8d0157ff7ced91fabb0ae41c3c92006abd49e9005ea78
4
- data.tar.gz: f8f243fb7f9e3ea2ec17fc795ad5bbd79b017f68665d98b911dfe27177e13389
3
+ metadata.gz: bca3de7432f33744351456a803dcbb74f73c0228f824dad035b51565c711e529
4
+ data.tar.gz: 0e0b3d2959f2db50c00d61a49ccad52be73711ac8e07cfd7cfffb186923fb5b2
5
5
  SHA512:
6
- metadata.gz: f2dbdd1c1811bfc09153a8eed32b95f337919d22434b004622a0ab78eb6803e68f5a66761e828bf2054421d5d5f9b798be54e7f60cc486602a997c804ab9fb2d
7
- data.tar.gz: 953672a066c27a231fb76c3c9839b4cb9685deb0ed4f25a3e153f4ede0833afa0a263f4d37532715a62ed7a96825384b8f7d6fef7352416d0c64068864798fee
6
+ metadata.gz: ad760ae64fe74a08943013b9359e675bf46ad366cb9a688937a182e2fc8b6abd336fa8b8408cc61e22cf7c3ab713d0fa9e0a6ae973196d653e5c763818ef1672
7
+ data.tar.gz: 630f2765587b5b675eaf1e33b6c5b39a83ada8980a0638011e2cc71f4ce963d6f6287db5d50a9c5efb3d1ba7c1402e7ef1f2beb11369ad981a6046c8e4491f30
data/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.8.1](https://github.com/bensheldon/good_job/tree/v2.8.1) (2022-01-03)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.8.0...v2.8.1)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - 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))
10
+
11
+ **Closed issues:**
12
+
13
+ - 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)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - 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))
18
+ - Clean up server integration tests [\#474](https://github.com/bensheldon/good_job/pull/474) ([bensheldon](https://github.com/bensheldon))
19
+
20
+ ## [v2.8.0](https://github.com/bensheldon/good_job/tree/v2.8.0) (2021-12-31)
21
+
22
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.4...v2.8.0)
23
+
24
+ **Implemented enhancements:**
25
+
26
+ - GoodJob should automatically clean up after itself and delete old job records [\#412](https://github.com/bensheldon/good_job/issues/412)
27
+ - Track processes in the database and on the Dashboard [\#472](https://github.com/bensheldon/good_job/pull/472) ([bensheldon](https://github.com/bensheldon))
28
+ - 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))
29
+
30
+ **Closed issues:**
31
+
32
+ - Is there a way to show how many worker/process is running currently [\#471](https://github.com/bensheldon/good_job/issues/471)
33
+ - Jobs stuck in the unfinished state [\#448](https://github.com/bensheldon/good_job/issues/448)
34
+
35
+ **Merged pull requests:**
36
+
37
+ - Doublequote Ruby 3.0 in testing matrix [\#473](https://github.com/bensheldon/good_job/pull/473) ([bensheldon](https://github.com/bensheldon))
38
+ - Have demo CleanupJob use GoodJob.cleanup\_preserved\_jobs [\#470](https://github.com/bensheldon/good_job/pull/470) ([bensheldon](https://github.com/bensheldon))
39
+ - Test with Rails 7.0.0 [\#469](https://github.com/bensheldon/good_job/pull/469) ([aried3r](https://github.com/aried3r))
40
+
41
+ ## [v2.7.4](https://github.com/bensheldon/good_job/tree/v2.7.4) (2021-12-16)
42
+
43
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.3...v2.7.4)
44
+
45
+ **Fixed bugs:**
46
+
47
+ - Add nonce: true to javascript\_include\_tag in dashboard [\#468](https://github.com/bensheldon/good_job/pull/468) ([bouk](https://github.com/bouk))
48
+
49
+ **Closed issues:**
50
+
51
+ - Add nonce: true to engine views [\#467](https://github.com/bensheldon/good_job/issues/467)
52
+ - Updating good\_job breaks my Rails 7 alpha 2 local development [\#462](https://github.com/bensheldon/good_job/issues/462)
53
+
54
+ **Merged pull requests:**
55
+
56
+ - Update appraisal for Rails 7.0.0.rc1 [\#466](https://github.com/bensheldon/good_job/pull/466) ([bensheldon](https://github.com/bensheldon))
57
+
58
+ ## [v2.7.3](https://github.com/bensheldon/good_job/tree/v2.7.3) (2021-11-30)
59
+
60
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.2...v2.7.3)
61
+
62
+ **Fixed bugs:**
63
+
64
+ - Logger error on 2.7.2 [\#463](https://github.com/bensheldon/good_job/issues/463)
65
+ - 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))
66
+
3
67
  ## [v2.7.2](https://github.com/bensheldon/good_job/tree/v2.7.2) (2021-11-29)
4
68
 
5
69
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.1...v2.7.2)
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,6 +11,11 @@ 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
14
+ include ActiveSupport::Callbacks
15
+ define_callbacks :listen, :unlisten
16
+
17
+ include Notifier::ProcessRegistration
18
+
13
19
  # Raised if the Database adapter does not implement LISTEN.
14
20
  AdapterCannotListenError = Class.new(StandardError)
15
21
 
@@ -43,6 +49,12 @@ module GoodJob # :nodoc:
43
49
  # @return [Array<GoodJob::Notifier>, nil]
44
50
  cattr_reader :instances, default: [], instance_reader: false
45
51
 
52
+ # @!attribute [rw] connection
53
+ # @!scope class
54
+ # ActiveRecord Connection that has been established for the Notifier.
55
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter, nil]
56
+ thread_cattr_accessor :connection
57
+
46
58
  # Send a message via Postgres NOTIFY
47
59
  # @param message [#to_json]
48
60
  def self.notify(message)
@@ -146,30 +158,36 @@ module GoodJob # :nodoc:
146
158
 
147
159
  def listen(delay: 0)
148
160
  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
161
+ with_connection do
162
+ begin
163
+ run_callbacks :listen do
164
+ ActiveSupport::Notifications.instrument("notifier_listen.good_job") do
165
+ connection.execute("LISTEN #{CHANNEL}")
166
+ end
167
+ thr_listening.make_true
168
+ end
153
169
 
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)
170
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads do
171
+ while thr_executor.running?
172
+ wait_for_notify do |channel, payload|
173
+ next unless channel == CHANNEL
174
+
175
+ ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
176
+ parsed_payload = JSON.parse(payload, symbolize_names: true)
177
+ thr_recipients.each do |recipient|
178
+ target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
179
+ target.send(method_name, parsed_payload)
180
+ end
165
181
  end
166
182
  end
167
183
  end
168
184
  end
169
185
  ensure
170
- thr_listening.make_false
171
- ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
172
- conn.async_exec("UNLISTEN *").clear
186
+ run_callbacks :unlisten do
187
+ thr_listening.make_false
188
+ ActiveSupport::Notifications.instrument("notifier_unlisten.good_job") do
189
+ connection.execute("UNLISTEN *")
190
+ end
173
191
  end
174
192
  end
175
193
  end
@@ -178,17 +196,27 @@ module GoodJob # :nodoc:
178
196
  future.execute
179
197
  end
180
198
 
181
- def with_listen_connection
182
- ar_conn = Execution.connection_pool.checkout.tap do |conn|
199
+ def with_connection
200
+ self.connection = Execution.connection_pool.checkout.tap do |conn|
183
201
  Execution.connection_pool.remove(conn)
184
202
  end
185
- pg_conn = ar_conn.raw_connection
186
- raise AdapterCannotListenError unless pg_conn.respond_to? :wait_for_notify
203
+ connection.execute("SET application_name = #{connection.quote(self.class.name)}")
187
204
 
188
- pg_conn.async_exec("SET application_name = #{pg_conn.escape_identifier(self.class.name)}").clear
189
- yield pg_conn
205
+ yield
190
206
  ensure
191
- ar_conn&.disconnect!
207
+ connection&.disconnect!
208
+ self.connection = nil
209
+ end
210
+
211
+ def wait_for_notify
212
+ raw_connection = connection.raw_connection
213
+ if raw_connection.respond_to?(:wait_for_notify)
214
+ raw_connection.wait_for_notify(WAIT_INTERVAL) do |channel, _pid, payload|
215
+ yield(channel, payload)
216
+ end
217
+ else
218
+ sleep WAIT_INTERVAL
219
+ end
192
220
  end
193
221
  end
194
222
  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
@@ -24,10 +24,12 @@ module GoodJob
24
24
 
25
25
  initializer 'good_job.rails_config' do
26
26
  config.after_initialize do
27
- GoodJob.logger = Rails.application.config.good_job.logger unless Rails.application.config.good_job.logger.nil?
28
- GoodJob.on_thread_error = Rails.application.config.good_job.on_thread_error unless Rails.application.config.good_job.on_thread_error.nil?
29
- GoodJob.preserve_job_records = Rails.application.config.good_job.preserve_job_records unless Rails.application.config.good_job.preserve_job_records.nil?
30
- GoodJob.retry_on_unhandled_error = Rails.application.config.good_job.retry_on_unhandled_error unless Rails.application.config.good_job.retry_on_unhandled_error.nil?
27
+ rails_config = Rails.application.config.good_job
28
+
29
+ GoodJob.logger = rails_config[:logger] if rails_config.key?(:logger)
30
+ GoodJob.on_thread_error = rails_config[:on_thread_error] if rails_config.key?(:on_thread_error)
31
+ GoodJob.preserve_job_records = rails_config[:preserve_job_records] if rails_config.key?(:preserve_job_records)
32
+ GoodJob.retry_on_unhandled_error = rails_config[:retry_on_unhandled_error] if rails_config.key?(:retry_on_unhandled_error)
31
33
  end
32
34
  end
33
35
 
@@ -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.2'
4
+ VERSION = '2.8.1'
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.2
4
+ version: 2.8.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: 2021-11-29 00:00:00.000000000 Z
11
+ date: 2022-01-03 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