good_job 2.7.1 → 2.8.0

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: ad8ca019c816dee452b246cdaa6d806844bcbd6aca63da6e5d52c87828c9c8e4
4
- data.tar.gz: 343c128a0d8477688e8f38d214add4c341dbfce694c19c6c2acf9cf7bb5c5cef
3
+ metadata.gz: ae78ac0322802107488f4a8b55963665cf9b3f173eb05174382bf05afdcef5a6
4
+ data.tar.gz: 2f41dd00281bcf2d0a2c29f6124de36964dcc730436f1cbd2f4c740c873263ed
5
5
  SHA512:
6
- metadata.gz: d5153a01609aefefd4ac23319ba76755ba13ea64c749b9b582fda5a17a497c0d640e20e6db91aa9eb9b18042b62283acc8dd25c67f1389290291ae55c2187f33
7
- data.tar.gz: e145b2a1d26eaf4b07e60ca8826e39216de230569f197f0eafae481b7716754aeb95a4bce54330e15389612f49361f320c8934965beca9c4c60d97f1a84f3b05
6
+ metadata.gz: 0ec2d40fdd87f293f8372e27040c7b14f8668645f50bc968ce91ec758202a400bb7c4a9ecb2e31a5827d59717dc3069840d5402981e1dc9f98c113120e259258
7
+ data.tar.gz: 0dd784cee33ab996ad332d1bfc371c357ffe3fb9727629096cff7189cc391195c109400240569444221a2ebf3340e2ab0d2d1c7bbd2830ee1c5b2ada9117d995
data/CHANGELOG.md CHANGED
@@ -1,5 +1,66 @@
1
1
  # Changelog
2
2
 
3
+ ## [v2.8.0](https://github.com/bensheldon/good_job/tree/v2.8.0) (2021-12-31)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.4...v2.8.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - GoodJob should automatically clean up after itself and delete old job records [\#412](https://github.com/bensheldon/good_job/issues/412)
10
+ - Track processes in the database and on the Dashboard [\#472](https://github.com/bensheldon/good_job/pull/472) ([bensheldon](https://github.com/bensheldon))
11
+ - 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))
12
+
13
+ **Closed issues:**
14
+
15
+ - Is there a way to show how many worker/process is running currently [\#471](https://github.com/bensheldon/good_job/issues/471)
16
+ - Jobs stuck in the unfinished state [\#448](https://github.com/bensheldon/good_job/issues/448)
17
+
18
+ **Merged pull requests:**
19
+
20
+ - Doublequote Ruby 3.0 in testing matrix [\#473](https://github.com/bensheldon/good_job/pull/473) ([bensheldon](https://github.com/bensheldon))
21
+ - Have demo CleanupJob use GoodJob.cleanup\_preserved\_jobs [\#470](https://github.com/bensheldon/good_job/pull/470) ([bensheldon](https://github.com/bensheldon))
22
+ - Test with Rails 7.0.0 [\#469](https://github.com/bensheldon/good_job/pull/469) ([aried3r](https://github.com/aried3r))
23
+
24
+ ## [v2.7.4](https://github.com/bensheldon/good_job/tree/v2.7.4) (2021-12-16)
25
+
26
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.3...v2.7.4)
27
+
28
+ **Fixed bugs:**
29
+
30
+ - Add nonce: true to javascript\_include\_tag in dashboard [\#468](https://github.com/bensheldon/good_job/pull/468) ([bouk](https://github.com/bouk))
31
+
32
+ **Closed issues:**
33
+
34
+ - Add nonce: true to engine views [\#467](https://github.com/bensheldon/good_job/issues/467)
35
+ - Updating good\_job breaks my Rails 7 alpha 2 local development [\#462](https://github.com/bensheldon/good_job/issues/462)
36
+
37
+ **Merged pull requests:**
38
+
39
+ - Update appraisal for Rails 7.0.0.rc1 [\#466](https://github.com/bensheldon/good_job/pull/466) ([bensheldon](https://github.com/bensheldon))
40
+
41
+ ## [v2.7.3](https://github.com/bensheldon/good_job/tree/v2.7.3) (2021-11-30)
42
+
43
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.2...v2.7.3)
44
+
45
+ **Fixed bugs:**
46
+
47
+ - Logger error on 2.7.2 [\#463](https://github.com/bensheldon/good_job/issues/463)
48
+ - 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))
49
+
50
+ ## [v2.7.2](https://github.com/bensheldon/good_job/tree/v2.7.2) (2021-11-29)
51
+
52
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.1...v2.7.2)
53
+
54
+ **Implemented enhancements:**
55
+
56
+ - Allow GoodJob global configuration accessors to also be set via Rails config hash [\#460](https://github.com/bensheldon/good_job/pull/460) ([bensheldon](https://github.com/bensheldon))
57
+
58
+ **Merged pull requests:**
59
+
60
+ - Use `ActiveRecord::Relation::QueryAttribute` when setting up bindings for `exec_query` [\#461](https://github.com/bensheldon/good_job/pull/461) ([bensheldon](https://github.com/bensheldon))
61
+ - Configure RSpec `config.example_status_persistence_file_path` [\#459](https://github.com/bensheldon/good_job/pull/459) ([bensheldon](https://github.com/bensheldon))
62
+ - Defer async initialization until Rails fully initialized [\#454](https://github.com/bensheldon/good_job/pull/454) ([bensheldon](https://github.com/bensheldon))
63
+
3
64
  ## [v2.7.1](https://github.com/bensheldon/good_job/tree/v2.7.1) (2021-11-26)
4
65
 
5
66
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v2.7.0...v2.7.1)
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
 
@@ -97,7 +98,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
97
98
  1. Configure the ActiveJob adapter:
98
99
 
99
100
  ```ruby
100
- # config/application.rb
101
+ # config/application.rb or config/environments/{RAILS_ENV}.rb
101
102
  config.active_job.queue_adapter = :good_job
102
103
  ```
103
104
 
@@ -212,43 +213,48 @@ to delete old records and preserve space in your database.
212
213
 
213
214
  ### Configuration options
214
215
 
215
- To use GoodJob, you can set `config.active_job.queue_adapter` to a `:good_job`.
216
+ ActiveJob configuration depends on where the code is placed:
216
217
 
217
- Additional configuration can be provided via `config.good_job.OPTION = ...`.
218
+ - `config.active_job.queue_adapter = :good_job` within `config/application.rb` or `config/environments/*.rb`.
219
+ - `ActiveJob::Base.queue_adapter = :good_job` within an initializer (e.g. `config/initializers/active_job.rb`).
218
220
 
219
- _Configuration **must** be placed into `config/application.rb` or `config/environments/{RAILS_ENV}.rb`; configuration may not work correctly if placed into `config/initializers/*.rb` because application initializers run _after_ gem initialization (see [Rails#36650](https://github.com/rails/rails/issues/36650) and [GoodJob#380](https://github.com/bensheldon/good_job/issues/380))._
221
+ GoodJob configuration can be placed within Rails `config` directory for all environments (`config/application.rb`), within a particular environment (e.g. `config/environments/development.rb`), or within an initializer (e.g. `config/initializers/good_job.rb`).
220
222
 
221
223
  Configuration examples:
222
224
 
223
225
  ```ruby
224
- # config/application.rb
225
-
226
- config.active_job.queue_adapter = :good_job
227
-
228
- # Configure options individually...
229
- config.good_job.execution_mode = :async
230
- config.good_job.max_threads = 5
231
- config.good_job.poll_interval = 30 # seconds
232
- config.good_job.shutdown_timeout = 25 # seconds
233
- config.good_job.enable_cron = true
234
- config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
235
- config.good_job.queues = '*'
236
-
237
- # ...or all at once.
238
- config.good_job = {
239
- execution_mode: :async,
240
- max_threads: 5,
241
- poll_interval: 30,
242
- shutdown_timeout: 25,
243
- enable_cron: true,
244
- cron: {
245
- example: {
246
- cron: '0 * * * *',
247
- class: 'ExampleJob'
226
+ Rails.application.configure do
227
+ # Configure options individually...
228
+ config.good_job.preserve_job_records = true
229
+ config.good_job.retry_on_unhandled_error = false
230
+ config.good_job.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
231
+ config.good_job.execution_mode = :async
232
+ config.good_job.max_threads = 5
233
+ config.good_job.poll_interval = 30 # seconds
234
+ config.good_job.shutdown_timeout = 25 # seconds
235
+ config.good_job.enable_cron = true
236
+ config.good_job.cron = { example: { cron: '0 * * * *', class: 'ExampleJob' } }
237
+ config.good_job.queues = '*'
238
+
239
+ # ...or all at once.
240
+ config.good_job = {
241
+ preserve_job_records: true,
242
+ retry_on_unhandled_error: false,
243
+ on_thread_error: -> (exception) { Raven.capture_exception(exception) },
244
+ execution_mode: :async,
245
+ max_threads: 5,
246
+ poll_interval: 30,
247
+ shutdown_timeout: 25,
248
+ enable_cron: true,
249
+ cron: {
250
+ example: {
251
+ cron: '0 * * * *',
252
+ class: 'ExampleJob'
253
+ },
248
254
  },
249
- },
250
- queues: '*',
251
- }
255
+ queues: '*',
256
+ }
257
+ end
252
258
  ```
253
259
 
254
260
  Available configuration options are:
@@ -265,6 +271,17 @@ Available configuration options are:
265
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`.
266
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`.
267
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`.
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`).
278
+ - `preserve_job_records` (boolean) keeps job records in your database even after jobs are completed. (Default: `false`)
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`)
280
+ - `on_thread_error` (proc, lambda, or callable) will be called when an Exception. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake. Example:
281
+
282
+ ```ruby
283
+ config.good_job.on_thread_error = -> (exception) { Raven.capture_exception(exception) }
284
+ ```
268
285
 
269
286
  By default, GoodJob configures the following execution modes per environment:
270
287
 
@@ -285,7 +302,7 @@ config.good_job.execution_mode = :external
285
302
 
286
303
  ### Global options
287
304
 
288
- Good Job’s general behavior can also be configured via several attributes directly on the `GoodJob` module:
305
+ Good Job’s general behavior can also be configured via attributes directly on the `GoodJob` module:
289
306
 
290
307
  - **`GoodJob.active_record_parent_class`** (string) The ActiveRecord parent class inherited by GoodJob's ActiveRecord model `GoodJob::Job` (defaults to `"ActiveRecord::Base"`). Configure this when using [multiple databases with ActiveRecord](https://guides.rubyonrails.org/active_record_multiple_databases.html) or when other custom configuration is necessary for the ActiveRecord model to connect to the Postgres database. _The value must be a String to avoid premature initialization of ActiveRecord._
291
308
  - **`GoodJob.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`.
@@ -30,7 +30,10 @@ module GoodJob
30
30
  ORDER BY timestamp ASC
31
31
  SQL
32
32
 
33
- binds = [[nil, start_time], [nil, end_time]]
33
+ binds = [
34
+ ActiveRecord::Relation::QueryAttribute.new('start_time', start_time, ActiveRecord::Type::DateTime.new),
35
+ ActiveRecord::Relation::QueryAttribute.new('end_time', end_time, ActiveRecord::Type::DateTime.new),
36
+ ]
34
37
  executions_data = GoodJob::Execution.connection.exec_query(GoodJob::Execution.pg_or_jdbc_query(count_query), "GoodJob Dashboard Chart", binds)
35
38
 
36
39
  queue_names = executions_data.reject { |d| d['count'].nil? }.map { |d| d['queue_name'] || BaseFilter::EMPTY }.uniq
@@ -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,6 +21,11 @@ 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
@@ -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
@@ -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
 
@@ -4,6 +4,12 @@ module GoodJob
4
4
  # ActiveJob Adapter.
5
5
  #
6
6
  class Adapter
7
+ # @!attribute [r] instances
8
+ # @!scope class
9
+ # List of all instantiated Adapters in the current process.
10
+ # @return [Array<GoodJob::Adapter>, nil]
11
+ cattr_reader :instances, default: [], instance_reader: false
12
+
7
13
  # @param execution_mode [Symbol, nil] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
8
14
  #
9
15
  # - +:inline+ executes jobs immediately in whatever process queued them (usually the web server process). This should only be used in test and development environments.
@@ -20,7 +26,8 @@ module GoodJob
20
26
  # @param max_threads [Integer, nil] sets the number of threads per scheduler to use when +execution_mode+ is set to +:async+. The +queues+ parameter can specify a number of threads for each group of queues which will override this value. You can also set this with the environment variable +GOOD_JOB_MAX_THREADS+. Defaults to +5+.
21
27
  # @param queues [String, nil] determines which queues to execute jobs from when +execution_mode+ is set to +:async+. See {file:README.md#optimize-queues-threads-and-processes} for more details on the format of this string. You can also set this with the environment variable +GOOD_JOB_QUEUES+. Defaults to +"*"+.
22
28
  # @param poll_interval [Integer, nil] sets the number of seconds between polls for jobs when +execution_mode+ is set to +:async+. You can also set this with the environment variable +GOOD_JOB_POLL_INTERVAL+. Defaults to +1+.
23
- def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil)
29
+ # @param start_async_on_initialize [Boolean] whether to start the async scheduler when the adapter is initialized.
30
+ def initialize(execution_mode: nil, queues: nil, max_threads: nil, poll_interval: nil, start_async_on_initialize: Rails.application.initialized?)
24
31
  @configuration = GoodJob::Configuration.new(
25
32
  {
26
33
  execution_mode: execution_mode,
@@ -30,16 +37,9 @@ module GoodJob
30
37
  }
31
38
  )
32
39
  @configuration.validate!
40
+ self.class.instances << self
33
41
 
34
- if execute_async? # rubocop:disable Style/GuardClause
35
- @notifier = GoodJob::Notifier.new
36
- @poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
37
- @scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: Rails.application.initialized?)
38
- @notifier.recipients << [@scheduler, :create_thread]
39
- @poller.recipients << [@scheduler, :create_thread]
40
-
41
- @cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: Rails.application.initialized?) if @configuration.enable_cron?
42
- end
42
+ start_async if start_async_on_initialize
43
43
  end
44
44
 
45
45
  # Enqueues the ActiveJob job to be performed.
@@ -74,7 +74,7 @@ module GoodJob
74
74
  job_state = { queue_name: execution.queue_name }
75
75
  job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
76
76
 
77
- executed_locally = execute_async? && @scheduler.create_thread(job_state)
77
+ executed_locally = execute_async? && @scheduler&.create_thread(job_state)
78
78
  Notifier.notify(job_state) unless executed_locally
79
79
  end
80
80
 
@@ -97,6 +97,7 @@ module GoodJob
97
97
 
98
98
  executables = [@notifier, @poller, @scheduler].compact
99
99
  GoodJob._shutdown_all(executables, timeout: timeout)
100
+ @_async_started = false
100
101
  end
101
102
 
102
103
  # Whether in +:async+ execution mode.
@@ -119,6 +120,28 @@ module GoodJob
119
120
  @configuration.execution_mode == :inline
120
121
  end
121
122
 
123
+ # Start async executors
124
+ # @return [void]
125
+ def start_async
126
+ return unless execute_async?
127
+
128
+ @notifier = GoodJob::Notifier.new
129
+ @poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
130
+ @scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: true)
131
+ @notifier.recipients << [@scheduler, :create_thread]
132
+ @poller.recipients << [@scheduler, :create_thread]
133
+
134
+ @cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true) if @configuration.enable_cron?
135
+
136
+ @_async_started = true
137
+ end
138
+
139
+ # Whether the async executors are running
140
+ # @return [Boolean]
141
+ def async_started?
142
+ @_async_started
143
+ end
144
+
122
145
  private
123
146
 
124
147
  # Whether running in a web server process.
@@ -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
data/lib/good_job/cli.rb CHANGED
@@ -96,6 +96,7 @@ module GoodJob
96
96
  poller.recipients << [scheduler, :create_thread]
97
97
 
98
98
  cron_manager = GoodJob::CronManager.new(configuration.cron_entries, start_on_initialize: true) if configuration.enable_cron?
99
+
99
100
  if configuration.probe_port
100
101
  probe_server = GoodJob::ProbeServer.new(port: configuration.probe_port)
101
102
  probe_server.start