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 +4 -4
- data/CHANGELOG.md +67 -3
- data/README.md +4 -0
- data/engine/app/controllers/good_job/processes_controller.rb +8 -0
- data/engine/app/views/good_job/processes/index.html.erb +40 -0
- data/engine/app/views/layouts/good_job/base.html.erb +7 -16
- data/engine/config/routes.rb +9 -6
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +7 -0
- data/lib/generators/good_job/templates/update/migrations/04_create_good_job_processes.rb.erb +17 -0
- data/lib/generators/good_job/templates/update/migrations/04_index_good_job_jobs_on_finished_at.rb.erb +25 -0
- data/lib/good_job/active_job_job.rb +10 -7
- data/lib/good_job/adapter.rb +2 -2
- data/lib/good_job/assignable_connection.rb +38 -0
- data/lib/good_job/base_record.rb +20 -0
- data/lib/good_job/cleanup_tracker.rb +38 -0
- data/lib/good_job/configuration.rb +26 -0
- data/lib/good_job/current_thread.rb +1 -1
- data/lib/good_job/daemon.rb +3 -3
- data/lib/good_job/execution.rb +3 -14
- data/lib/good_job/job_performer.rb +6 -0
- data/lib/good_job/notifier/process_registration.rb +34 -0
- data/lib/good_job/notifier.rb +61 -29
- data/lib/good_job/process.rb +82 -0
- data/lib/good_job/scheduler.rb +39 -4
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +5 -3
- metadata +13 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba4a26a2047c0e7dd8b78264fee7c6d2c05c2dd59e38f1df402b698826b56288
|
|
4
|
+
data.tar.gz: 3bd4c49139941c2d37751abb9d32808b0efd7a33370aa7370afeed21cc845ab0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2ade6a5c9b26e1d9d1bc1522dc10159f78943b5da249effa1405dccf5c3de5078f74a08c27657eef63d51967bb04eb8486d933e9d817664a25c33f3740f2280
|
|
7
|
+
data.tar.gz: 40dc9f0b3b1d210ddcec9d7ce5dd7014e2409d5602bb4b3c55314b5bf799ef29fab9fd0764931c2026242c91383a335b64c4f3243ea16d50997e59ef9c7820e3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,79 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [v2.
|
|
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.
|
|
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
|
-
-
|
|
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
|
[](https://rubygems.org/gems/good_job)
|
|
4
4
|
[](https://github.com/bensheldon/good_job/actions)
|
|
5
|
+
[](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,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>
|
data/engine/config/routes.rb
CHANGED
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
GoodJob::Engine.routes.draw do
|
|
3
3
|
root to: 'executions#index'
|
|
4
4
|
|
|
5
|
-
resources :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/good_job/adapter.rb
CHANGED
|
@@ -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?(
|
|
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?
|
data/lib/good_job/daemon.rb
CHANGED
|
@@ -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
|
data/lib/good_job/execution.rb
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
module GoodJob
|
|
3
3
|
# ActiveRecord model that represents an +ActiveJob+ job.
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
data/lib/good_job/notifier.rb
CHANGED
|
@@ -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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
yield pg_conn
|
|
200
|
+
yield
|
|
190
201
|
ensure
|
|
191
|
-
|
|
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
|
data/lib/good_job/scheduler.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
@
|
|
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
|
-
|
|
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
|
|
data/lib/good_job/version.rb
CHANGED
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
|
|
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
|
-
|
|
141
|
+
old_jobs = GoodJob::ActiveJobJob.where('finished_at <= ?', timestamp)
|
|
142
|
+
old_jobs_count = old_jobs.count
|
|
142
143
|
|
|
143
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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
|