good_job 3.3.2 → 3.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +45 -0
- data/README.md +1 -1
- data/app/controllers/good_job/cron_entries_controller.rb +20 -0
- data/app/filters/good_job/base_filter.rb +8 -7
- data/app/filters/good_job/jobs_filter.rb +14 -13
- data/app/models/good_job/base_record.rb +10 -0
- data/app/models/good_job/cron_entry.rb +14 -0
- data/app/models/good_job/process.rb +1 -0
- data/app/models/good_job/setting.rb +30 -0
- data/app/views/good_job/cron_entries/index.html.erb +13 -3
- data/app/views/good_job/shared/_filter.erb +42 -40
- data/app/views/good_job/shared/icons/_pause.html.erb +4 -0
- data/config/routes.rb +2 -0
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +7 -0
- data/lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb +19 -0
- data/lib/good_job/adapter.rb +8 -4
- data/lib/good_job/configuration.rb +28 -0
- data/lib/good_job/cron_manager.rb +1 -1
- data/lib/good_job/notifier.rb +20 -1
- data/lib/good_job/version.rb +1 -1
- metadata +5 -3
- data/lib/good_job/enqueuing.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38942c9c17765023398fe2a5c2f3dc2c0ebadc799858ed1102bc47bd11d70ed9
|
4
|
+
data.tar.gz: 1dbbff4895a344d948592ec8801e1a325f48b984b66738efdbf785ca4f46f9b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03ac67ff433d1124b3d1dfcb07fb61d9b47393d4dcc8f3341698d5eead8597611009f453ea7cdc09f12cab2b1fbd1a6ed04fcb14fbe532aed8d0ac9fdb9c33f0
|
7
|
+
data.tar.gz: 0d951f8f156fbc47df60271d8f845df9ee93980af9ce3153a81fb8e3c7c0b4f80cbe7f727189be7d06930b97e30b89c65bf7d15fbe39138044979c8c4b319c8d
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,50 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.4.1](https://github.com/bensheldon/good_job/tree/v3.4.1) (2022-08-06)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.0...v3.4.1)
|
6
|
+
|
7
|
+
**Closed issues:**
|
8
|
+
|
9
|
+
- Add `cron_enabled` to Process state [\#673](https://github.com/bensheldon/good_job/issues/673)
|
10
|
+
- Good job is using a lot of memory / ram [\#613](https://github.com/bensheldon/good_job/issues/613)
|
11
|
+
- ActiveRecord::StatementInvalid: PG::ConnectionBad: PQsocket\(\) can't get socket descriptor every 30 minutes aprox. [\#579](https://github.com/bensheldon/good_job/issues/579)
|
12
|
+
|
13
|
+
**Merged pull requests:**
|
14
|
+
|
15
|
+
- Only report Notifier connection errors once after they happen 3 consecutive times [\#689](https://github.com/bensheldon/good_job/pull/689) ([bensheldon](https://github.com/bensheldon))
|
16
|
+
|
17
|
+
## [v3.4.0](https://github.com/bensheldon/good_job/tree/v3.4.0) (2022-08-05)
|
18
|
+
|
19
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.3.3...v3.4.0)
|
20
|
+
|
21
|
+
**Implemented enhancements:**
|
22
|
+
|
23
|
+
- Add cron\_enabled attribute to good\_job and pass it to process current state [\#675](https://github.com/bensheldon/good_job/pull/675) ([saksham-jain](https://github.com/saksham-jain))
|
24
|
+
- Reverse Dashboard Filter Hierarchy to be: queues+jobs then state [\#666](https://github.com/bensheldon/good_job/pull/666) ([bensheldon](https://github.com/bensheldon))
|
25
|
+
- Allow cron entries to be temporarily disabled and re-enabled through the Dashboard [\#649](https://github.com/bensheldon/good_job/pull/649) ([alex-klepa](https://github.com/alex-klepa))
|
26
|
+
- Add Configuration.total\_estimated\_threads to report number of threads consumed by GoodJob [\#645](https://github.com/bensheldon/good_job/pull/645) ([bensheldon](https://github.com/bensheldon))
|
27
|
+
|
28
|
+
**Closed issues:**
|
29
|
+
|
30
|
+
- Cron Schedule jobs add disable action [\#540](https://github.com/bensheldon/good_job/issues/540)
|
31
|
+
|
32
|
+
**Merged pull requests:**
|
33
|
+
|
34
|
+
- Removed text that implied an existing feature had not been finished [\#688](https://github.com/bensheldon/good_job/pull/688) ([pgvsalamander](https://github.com/pgvsalamander))
|
35
|
+
|
36
|
+
## [v3.3.3](https://github.com/bensheldon/good_job/tree/v3.3.3) (2022-08-02)
|
37
|
+
|
38
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.3.2...v3.3.3)
|
39
|
+
|
40
|
+
**Fixed bugs:**
|
41
|
+
|
42
|
+
- Detect usage of `puma` CLI for async mode [\#686](https://github.com/bensheldon/good_job/pull/686) ([bensheldon](https://github.com/bensheldon))
|
43
|
+
|
44
|
+
**Closed issues:**
|
45
|
+
|
46
|
+
- Async not working Rails 7 with puma CLI [\#685](https://github.com/bensheldon/good_job/issues/685)
|
47
|
+
|
3
48
|
## [v3.3.2](https://github.com/bensheldon/good_job/tree/v3.3.2) (2022-07-27)
|
4
49
|
|
5
50
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.3.1...v3.3.2)
|
data/README.md
CHANGED
@@ -472,7 +472,7 @@ config.good_job.cron = {
|
|
472
472
|
args: [42, "life"], # positional arguments to pass; can also be a proc e.g. `-> { [Time.now] }`
|
473
473
|
kwargs: { name: "Alice" }, # keyword arguments to pass; can also be a proc e.g. `-> { { name: NAMES.sample } }`
|
474
474
|
set: { priority: -10 }, # additional ActiveJob properties; can also be a lambda/proc e.g. `-> { { priority: [1,2].sample } }`
|
475
|
-
description: "Something helpful", # optional description that appears in Dashboard
|
475
|
+
description: "Something helpful", # optional description that appears in Dashboard
|
476
476
|
},
|
477
477
|
another_task: {
|
478
478
|
cron: "0 0,12 * * *",
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module GoodJob
|
3
3
|
class CronEntriesController < GoodJob::ApplicationController
|
4
|
+
before_action :check_settings_migration!, only: [:enable, :disable]
|
5
|
+
|
4
6
|
def index
|
5
7
|
@cron_entries = CronEntry.all
|
6
8
|
end
|
@@ -15,5 +17,23 @@ module GoodJob
|
|
15
17
|
@cron_entry.enqueue(Time.current)
|
16
18
|
redirect_back(fallback_location: cron_entries_path, notice: "Cron entry has been enqueued.")
|
17
19
|
end
|
20
|
+
|
21
|
+
def enable
|
22
|
+
@cron_entry = CronEntry.find(params[:cron_key])
|
23
|
+
@cron_entry.enable
|
24
|
+
redirect_back(fallback_location: cron_entries_path, notice: "Cron entry has been enabled.")
|
25
|
+
end
|
26
|
+
|
27
|
+
def disable
|
28
|
+
@cron_entry = CronEntry.find(params[:cron_key])
|
29
|
+
@cron_entry.disable
|
30
|
+
redirect_back(fallback_location: cron_entries_path, notice: "Cron entry has been disabled.")
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def check_settings_migration!
|
36
|
+
redirect_back(fallback_location: cron_entries_path, alert: "Requires pending GoodJob database migration.") unless GoodJob::Setting.migrated?
|
37
|
+
end
|
18
38
|
end
|
19
39
|
end
|
@@ -24,18 +24,19 @@ module GoodJob
|
|
24
24
|
@_last ||= records.last
|
25
25
|
end
|
26
26
|
|
27
|
-
def job_classes
|
28
|
-
base_query.group("serialized_params->>'job_class'").count
|
29
|
-
.sort_by { |name, _count| name.to_s }
|
30
|
-
.to_h
|
31
|
-
end
|
32
|
-
|
33
27
|
def queues
|
34
28
|
base_query.group(:queue_name).count
|
35
29
|
.sort_by { |name, _count| name.to_s || EMPTY }
|
36
30
|
.to_h
|
37
31
|
end
|
38
32
|
|
33
|
+
def job_classes
|
34
|
+
filtered_query(params.slice(:queue_name)).unscope(:select)
|
35
|
+
.group("serialized_params->>'job_class'").count
|
36
|
+
.sort_by { |name, _count| name.to_s }
|
37
|
+
.to_h
|
38
|
+
end
|
39
|
+
|
39
40
|
def states
|
40
41
|
raise NotImplementedError
|
41
42
|
end
|
@@ -51,7 +52,7 @@ module GoodJob
|
|
51
52
|
}.merge(override).delete_if { |_, v| v.blank? }
|
52
53
|
end
|
53
54
|
|
54
|
-
def filtered_query
|
55
|
+
def filtered_query(filtered_params = params)
|
55
56
|
raise NotImplementedError
|
56
57
|
end
|
57
58
|
|
@@ -2,26 +2,27 @@
|
|
2
2
|
module GoodJob
|
3
3
|
class JobsFilter < BaseFilter
|
4
4
|
def states
|
5
|
+
query = filtered_query(params.except(:state)).unscope(:select)
|
5
6
|
{
|
6
|
-
'scheduled' =>
|
7
|
-
'retried' =>
|
8
|
-
'queued' =>
|
9
|
-
'running' =>
|
10
|
-
'finished' =>
|
11
|
-
'discarded' =>
|
7
|
+
'scheduled' => query.scheduled.count,
|
8
|
+
'retried' => query.retried.count,
|
9
|
+
'queued' => query.queued.count,
|
10
|
+
'running' => query.running.count,
|
11
|
+
'finished' => query.finished.count,
|
12
|
+
'discarded' => query.discarded.count,
|
12
13
|
}
|
13
14
|
end
|
14
15
|
|
15
|
-
def filtered_query
|
16
|
+
def filtered_query(filter_params = params)
|
16
17
|
query = base_query.includes(:executions).includes_advisory_locks
|
17
18
|
|
18
|
-
query = query.job_class(
|
19
|
-
query = query.where(queue_name:
|
20
|
-
query = query.search_text(
|
21
|
-
query = query.where(cron_key:
|
19
|
+
query = query.job_class(filter_params[:job_class]) if filter_params[:job_class].present?
|
20
|
+
query = query.where(queue_name: filter_params[:queue_name]) if filter_params[:queue_name].present?
|
21
|
+
query = query.search_text(filter_params[:query]) if filter_params[:query].present?
|
22
|
+
query = query.where(cron_key: filter_params[:cron_key]) if filter_params[:cron_key].present?
|
22
23
|
|
23
|
-
if
|
24
|
-
case
|
24
|
+
if filter_params[:state]
|
25
|
+
case filter_params[:state]
|
25
26
|
when 'discarded'
|
26
27
|
query = query.discarded
|
27
28
|
when 'finished'
|
@@ -16,5 +16,15 @@ module GoodJob
|
|
16
16
|
DEPRECATION
|
17
17
|
nil
|
18
18
|
end
|
19
|
+
|
20
|
+
# Checks for whether the schema is up to date.
|
21
|
+
# Can be overriden by child class.
|
22
|
+
# @return [Boolean]
|
23
|
+
def self.migrated?
|
24
|
+
return true if connection.table_exists?(table_name)
|
25
|
+
|
26
|
+
migration_pending_warning!
|
27
|
+
false
|
28
|
+
end
|
19
29
|
end
|
20
30
|
end
|
@@ -86,6 +86,20 @@ module GoodJob # :nodoc:
|
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
89
|
+
def enabled?
|
90
|
+
return true unless GoodJob::Setting.migrated?
|
91
|
+
|
92
|
+
GoodJob::Setting.cron_key_enabled?(key)
|
93
|
+
end
|
94
|
+
|
95
|
+
def enable
|
96
|
+
GoodJob::Setting.cron_key_enable(key)
|
97
|
+
end
|
98
|
+
|
99
|
+
def disable
|
100
|
+
GoodJob::Setting.cron_key_disable(key)
|
101
|
+
end
|
102
|
+
|
89
103
|
def enqueue(cron_at = nil)
|
90
104
|
GoodJob::CurrentThread.within do |current_thread|
|
91
105
|
current_thread.cron_key = key
|
@@ -48,6 +48,7 @@ module GoodJob # :nodoc:
|
|
48
48
|
preserve_job_records: GoodJob.preserve_job_records,
|
49
49
|
retry_on_unhandled_error: GoodJob.retry_on_unhandled_error,
|
50
50
|
schedulers: GoodJob::Scheduler.instances.map(&:name),
|
51
|
+
cron_enabled: GoodJob.configuration.enable_cron?,
|
51
52
|
}
|
52
53
|
end
|
53
54
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob
|
4
|
+
class Setting < BaseRecord
|
5
|
+
CRON_KEYS_DISABLED = "cron_keys_disabled"
|
6
|
+
|
7
|
+
self.table_name = 'good_job_settings'
|
8
|
+
|
9
|
+
def self.cron_key_enabled?(key)
|
10
|
+
cron_disabled = find_by(key: CRON_KEYS_DISABLED)&.value || []
|
11
|
+
cron_disabled.exclude?(key.to_s)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cron_key_enable(key)
|
15
|
+
setting = GoodJob::Setting.find_by(key: CRON_KEYS_DISABLED)
|
16
|
+
return unless setting&.value&.include?(key.to_s)
|
17
|
+
|
18
|
+
setting.value.delete(key.to_s)
|
19
|
+
setting.save!
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.cron_key_disable(key)
|
23
|
+
setting = find_or_initialize_by(key: CRON_KEYS_DISABLED) do |record|
|
24
|
+
record.value = []
|
25
|
+
end
|
26
|
+
setting.value << key
|
27
|
+
setting.save!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -37,10 +37,20 @@
|
|
37
37
|
<% end %>
|
38
38
|
</div>
|
39
39
|
<div class="col d-flex gap-3 justify-content-end">
|
40
|
-
<%= button_to enqueue_cron_entry_path(cron_entry
|
41
|
-
<%=
|
42
|
-
Run Now
|
40
|
+
<%= button_to enqueue_cron_entry_path(cron_entry), method: :post, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: "Enqueue cron entry now" }, title: "Enqueue cron entry now", data: { confirm: "Confirm enqueue cron entry now" } do %>
|
41
|
+
<%= render_icon "skip_forward" %>
|
43
42
|
<% end %>
|
43
|
+
|
44
|
+
<% if cron_entry.enabled? %>
|
45
|
+
<%= button_to disable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: "Disable cron entry" }, title: "Disable cron entry", data: { confirm: "Confirm disable cron entry" } do %>
|
46
|
+
<%= render_icon "pause" %>
|
47
|
+
<% end %>
|
48
|
+
<% else %>
|
49
|
+
<%= button_to enable_cron_entry_path(cron_entry), method: :put, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: "Enable cron entry" }, title: "Enable cron entry", data: { confirm: "Confirm enable cron entry" } do %>
|
50
|
+
<%= render_icon "play" %>
|
51
|
+
<% end %>
|
52
|
+
<% end %>
|
53
|
+
|
44
54
|
<%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
|
45
55
|
title: "Inspect",
|
46
56
|
data: { bs_toggle: "collapse", bs_target: "##{dom_id(cron_entry, 'properties')}" },
|
@@ -2,14 +2,53 @@
|
|
2
2
|
<div class="bg-light break-out">
|
3
3
|
<h2 class="container-fluid pt-3 pb-2"><%= title %></h2>
|
4
4
|
|
5
|
-
|
5
|
+
<%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "container-fluid") do |form| %>
|
6
|
+
<%= hidden_field_tag :poll, params[:poll] %>
|
7
|
+
<%= hidden_field_tag :state, params[:state] %>
|
8
|
+
<div class="d-flex flex-row w-100">
|
9
|
+
<div class="me-2">
|
10
|
+
<select name="queue_name" id="job_queue_filter" class="form-select form-select-sm">
|
11
|
+
<option value="" <%= "selected='selected'" if params[:queue_name].blank? %>>All queues</option>
|
12
|
+
|
13
|
+
<% filter.queues.each do |name, count| %>
|
14
|
+
<option value="<%= name.to_param %>" <%= "selected='selected'" if params[:queue_name] == name %>><%= name %> (<%= number_with_delimiter(count) %>)</option>
|
15
|
+
<% end %>
|
16
|
+
</select>
|
17
|
+
</div>
|
18
|
+
|
19
|
+
<div class="me-2">
|
20
|
+
<select name="job_class" id="job_class_filter" class="form-select form-select-sm">
|
21
|
+
<option value="" <%= "selected='selected'" if params[:job_class].blank? %>>All jobs</option>
|
22
|
+
|
23
|
+
<% filter.job_classes.each do |name, count| %>
|
24
|
+
<option value="<%= name.to_param %>" <%= "selected='selected'" if params[:job_class] == name %>><%= name %> (<%= number_with_delimiter(count) %>)</option>
|
25
|
+
<% end %>
|
26
|
+
</select>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<div class="me-2 flex-fill">
|
30
|
+
<%= search_field_tag "query", params[:query], class: "form-control form-control-sm", placeholder: "Search by class, job id, job params, and error text." %>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
<div class="d-flex flex-col align-items-end">
|
34
|
+
<div>
|
35
|
+
<%= form.submit "Search", name: nil, class: "btn btn-primary btn-sm" %>
|
36
|
+
<%= link_to filter.to_params(job_class: nil, state: nil, queue_name: nil, query: nil), class: "btn btn-secondary btn-sm" do %>
|
37
|
+
Clear
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
</div>
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<ul class="nav nav-tabs bg-light px-3 my-3">
|
6
45
|
<li class="nav-item">
|
7
|
-
<%= link_to "All",
|
46
|
+
<%= link_to "All", filter.to_params(state: nil), class: "nav-link #{"active" unless params[:state].present?}" %>
|
8
47
|
</li>
|
9
48
|
|
10
49
|
<% filter.states.each do |name, count| %>
|
11
50
|
<li class="nav-item">
|
12
|
-
<%= link_to
|
51
|
+
<%= link_to filter.to_params(state: name), class: "nav-link #{"active" if params[:state] == name}" do %>
|
13
52
|
<%= t(name, scope: 'good_job.status') %>
|
14
53
|
<span class="badge bg-primary rounded-pill <%= "bg-secondary" if count == 0 %>"><%= number_with_delimiter(count) %></span>
|
15
54
|
<% end %>
|
@@ -18,43 +57,6 @@
|
|
18
57
|
</ul>
|
19
58
|
</div>
|
20
59
|
|
21
|
-
<%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "container-fluid") do |form| %>
|
22
|
-
<%= hidden_field_tag :poll, params[:poll] %>
|
23
|
-
<%= hidden_field_tag :state, params[:state] %>
|
24
|
-
<div class="d-flex flex-row w-100">
|
25
|
-
<div class="me-2">
|
26
|
-
<select name="job_class" id="job_class_filter" class="form-select form-select-sm">
|
27
|
-
<option value="" <%= "selected='selected'" if params[:job_class].blank? %>>All jobs</option>
|
28
|
-
|
29
|
-
<% filter.job_classes.each do |name, count| %>
|
30
|
-
<option value="<%= name.to_param %>" <%= "selected='selected'" if params[:job_class] == name %>><%= name %> (<%= count %>)</option>
|
31
|
-
<% end %>
|
32
|
-
</select>
|
33
|
-
</div>
|
34
|
-
|
35
|
-
<div class="me-2">
|
36
|
-
<select name="queue_name" id="job_queue_filter" class="form-select form-select-sm">
|
37
|
-
<option value="" <%= "selected='selected'" if params[:queue_name].blank? %>>All queues</option>
|
38
|
-
|
39
|
-
<% filter.queues.each do |name, count| %>
|
40
|
-
<option value="<%= name.to_param %>" <%= "selected='selected'" if params[:queue_name] == name %>><%= name %> (<%= count %>)</option>
|
41
|
-
<% end %>
|
42
|
-
</select>
|
43
|
-
</div>
|
44
|
-
|
45
|
-
<div class="me-2 flex-fill">
|
46
|
-
<%= search_field_tag "query", params[:query], class: "form-control form-control-sm", placeholder: "Search by class, job id, job params, and error text." %>
|
47
|
-
</div>
|
48
|
-
|
49
|
-
<div class="d-flex flex-col align-items-end">
|
50
|
-
<div>
|
51
|
-
<%= form.submit "Search", name: nil, class: "btn btn-primary btn-sm" %>
|
52
|
-
<%= link_to "Clear all", filter.to_params(job_class: nil, state: nil, queue_name: nil, query: nil), class: "btn btn-secondary btn-sm" %>
|
53
|
-
</div>
|
54
|
-
</div>
|
55
|
-
</div>
|
56
|
-
<% end %>
|
57
|
-
|
58
60
|
<%= javascript_tag nonce: true do %>
|
59
61
|
document.addEventListener("DOMContentLoaded", () => {
|
60
62
|
document.querySelectorAll("#job_class_filter, #job_queue_filter").forEach((filter) => {
|
@@ -0,0 +1,4 @@
|
|
1
|
+
<!-- https://icons.getbootstrap.com/icons/pause-btn/ -->
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-pause" viewBox="0 0 16 16">
|
3
|
+
<path d="M6 3.5a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5zm4 0a.5.5 0 0 1 .5.5v8a.5.5 0 0 1-1 0V4a.5.5 0 0 1 .5-.5z" />
|
4
|
+
</svg>
|
data/config/routes.rb
CHANGED
@@ -26,6 +26,13 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
26
26
|
t.jsonb :state
|
27
27
|
end
|
28
28
|
|
29
|
+
create_table :good_job_settings, id: :uuid do |t|
|
30
|
+
t.timestamps
|
31
|
+
t.text :key
|
32
|
+
t.jsonb :value
|
33
|
+
t.index :key, unique: true
|
34
|
+
end
|
35
|
+
|
29
36
|
add_index :good_jobs, :scheduled_at, where: "(finished_at IS NULL)", name: "index_good_jobs_on_scheduled_at"
|
30
37
|
add_index :good_jobs, [:queue_name, :scheduled_at], where: "(finished_at IS NULL)", name: :index_good_jobs_on_queue_name_and_scheduled_at
|
31
38
|
add_index :good_jobs, [:active_job_id, :created_at], name: :index_good_jobs_on_active_job_id_and_created_at
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class CreateGoodJobSettings < 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_settings)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :good_job_settings, id: :uuid do |t|
|
13
|
+
t.timestamps
|
14
|
+
t.text :key
|
15
|
+
t.jsonb :value
|
16
|
+
t.index :key, unique: true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -148,10 +148,14 @@ module GoodJob
|
|
148
148
|
def in_server_process?
|
149
149
|
return @_in_server_process if defined? @_in_server_process
|
150
150
|
|
151
|
-
@_in_server_process = Rails.const_defined?(:Server) ||
|
152
|
-
|
153
|
-
|
154
|
-
|
151
|
+
@_in_server_process = Rails.const_defined?(:Server) || begin
|
152
|
+
self_caller = caller
|
153
|
+
|
154
|
+
self_caller.grep(%r{config.ru}).any? || # EXAMPLE: config.ru:3:in `block in <main>' OR config.ru:3:in `new_from_string'
|
155
|
+
self_caller.grep(%r{puma/request}).any? || # EXAMPLE: puma-5.6.4/lib/puma/request.rb:76:in `handle_request'
|
156
|
+
self_caller.grep(%{/rack/handler/}).any? || # EXAMPLE: iodine-0.7.44/lib/rack/handler/iodine.rb:13:in `start'
|
157
|
+
(Concurrent.on_jruby? && self_caller.grep(%r{jruby/rack/rails_booter}).any?) # EXAMPLE: uri:classloader:/jruby/rack/rails_booter.rb:83:in `load_environment'
|
158
|
+
end
|
155
159
|
end
|
156
160
|
end
|
157
161
|
end
|
@@ -42,6 +42,34 @@ module GoodJob
|
|
42
42
|
# @return [Hash]
|
43
43
|
attr_reader :env
|
44
44
|
|
45
|
+
# Returns the maximum number of threads GoodJob might consume
|
46
|
+
# @param warn [Boolean] whether to print a warning when over the limit
|
47
|
+
# @return [Integer]
|
48
|
+
def self.total_estimated_threads(warn: false)
|
49
|
+
configuration = new({})
|
50
|
+
|
51
|
+
cron_threads = configuration.enable_cron? ? 2 : 0
|
52
|
+
notifier_threads = 1
|
53
|
+
scheduler_threads = GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats[:max_threads] }
|
54
|
+
|
55
|
+
good_job_threads = cron_threads + notifier_threads + scheduler_threads
|
56
|
+
puma_threads = (Puma::Server.current&.max_threads if defined?(Puma::Server)) || 0
|
57
|
+
|
58
|
+
total_threads = good_job_threads + puma_threads
|
59
|
+
activerecord_pool_size = ActiveRecord::Base.connection_pool&.size
|
60
|
+
|
61
|
+
if warn && activerecord_pool_size && total_threads > activerecord_pool_size
|
62
|
+
message = "GoodJob is using #{good_job_threads} threads, " \
|
63
|
+
"#{" and Puma is using #{puma_threads} threads, " if puma_threads.positive?}" \
|
64
|
+
"which is #{total_threads - activerecord_pool_size} thread(s) more than ActiveRecord's database connection pool size of #{activerecord_pool_size}. " \
|
65
|
+
"Consider increasing ActiveRecord's database connection pool size in config/database.yml."
|
66
|
+
|
67
|
+
GoodJob.logger.warn message
|
68
|
+
end
|
69
|
+
|
70
|
+
good_job_threads
|
71
|
+
end
|
72
|
+
|
45
73
|
# @param options [Hash] Any explicitly specified configuration options to
|
46
74
|
# use. Keys are symbols that match the various methods on this class.
|
47
75
|
# @param env [Hash] A +Hash+ from which to read environment variables that
|
data/lib/good_job/notifier.rb
CHANGED
@@ -40,6 +40,7 @@ module GoodJob # :nodoc:
|
|
40
40
|
PG::UnableToSend
|
41
41
|
PG::Error
|
42
42
|
].freeze
|
43
|
+
CONNECTION_ERRORS_REPORTING_THRESHOLD = 3
|
43
44
|
|
44
45
|
# @!attribute [r] instances
|
45
46
|
# @!scope class
|
@@ -70,6 +71,8 @@ module GoodJob # :nodoc:
|
|
70
71
|
def initialize(*recipients)
|
71
72
|
@recipients = Concurrent::Array.new(recipients)
|
72
73
|
@listening = Concurrent::AtomicBoolean.new(false)
|
74
|
+
@connection_errors_count = Concurrent::AtomicFixnum.new(0)
|
75
|
+
@connection_errors_reported = Concurrent::AtomicBoolean.new(false)
|
73
76
|
|
74
77
|
self.class.instances << self
|
75
78
|
|
@@ -128,7 +131,6 @@ module GoodJob # :nodoc:
|
|
128
131
|
# @return [void]
|
129
132
|
def listen_observer(_time, _result, thread_error)
|
130
133
|
if thread_error
|
131
|
-
GoodJob._on_thread_error(thread_error)
|
132
134
|
ActiveSupport::Notifications.instrument("notifier_notify_error.good_job", { error: thread_error })
|
133
135
|
|
134
136
|
connection_error = CONNECTION_ERRORS.any? do |error_string|
|
@@ -137,6 +139,16 @@ module GoodJob # :nodoc:
|
|
137
139
|
|
138
140
|
thread_error.is_a? error_class
|
139
141
|
end
|
142
|
+
|
143
|
+
if connection_error
|
144
|
+
@connection_errors_count.increment
|
145
|
+
if @connection_errors_reported.false? && @connection_errors_count.value >= CONNECTION_ERRORS_REPORTING_THRESHOLD
|
146
|
+
GoodJob._on_thread_error(thread_error)
|
147
|
+
@connection_errors_reported.make_true
|
148
|
+
end
|
149
|
+
else
|
150
|
+
GoodJob._on_thread_error(thread_error)
|
151
|
+
end
|
140
152
|
end
|
141
153
|
|
142
154
|
return if shutdown?
|
@@ -175,6 +187,8 @@ module GoodJob # :nodoc:
|
|
175
187
|
target.send(method_name, parsed_payload)
|
176
188
|
end
|
177
189
|
end
|
190
|
+
|
191
|
+
reset_connection_errors
|
178
192
|
end
|
179
193
|
end
|
180
194
|
end
|
@@ -223,5 +237,10 @@ module GoodJob # :nodoc:
|
|
223
237
|
sleep WAIT_INTERVAL
|
224
238
|
end
|
225
239
|
end
|
240
|
+
|
241
|
+
def reset_connection_errors
|
242
|
+
@connection_errors_count.value = 0
|
243
|
+
@connection_errors_reported.make_false
|
244
|
+
end
|
226
245
|
end
|
227
246
|
end
|
data/lib/good_job/version.rb
CHANGED
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: 3.
|
4
|
+
version: 3.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -379,6 +379,7 @@ files:
|
|
379
379
|
- app/models/good_job/job.rb
|
380
380
|
- app/models/good_job/lockable.rb
|
381
381
|
- app/models/good_job/process.rb
|
382
|
+
- app/models/good_job/setting.rb
|
382
383
|
- app/views/good_job/cron_entries/index.html.erb
|
383
384
|
- app/views/good_job/cron_entries/show.html.erb
|
384
385
|
- app/views/good_job/jobs/_executions.erb
|
@@ -398,6 +399,7 @@ files:
|
|
398
399
|
- app/views/good_job/shared/icons/_dots.html.erb
|
399
400
|
- app/views/good_job/shared/icons/_exclamation.html.erb
|
400
401
|
- app/views/good_job/shared/icons/_info.html.erb
|
402
|
+
- app/views/good_job/shared/icons/_pause.html.erb
|
401
403
|
- app/views/good_job/shared/icons/_play.html.erb
|
402
404
|
- app/views/good_job/shared/icons/_skip_forward.html.erb
|
403
405
|
- app/views/good_job/shared/icons/_stop.html.erb
|
@@ -413,6 +415,7 @@ files:
|
|
413
415
|
- lib/generators/good_job/install_generator.rb
|
414
416
|
- lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb
|
415
417
|
- lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb
|
418
|
+
- lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb
|
416
419
|
- lib/generators/good_job/update_generator.rb
|
417
420
|
- lib/good_job.rb
|
418
421
|
- lib/good_job/active_job_extensions/concurrency.rb
|
@@ -426,7 +429,6 @@ files:
|
|
426
429
|
- lib/good_job/daemon.rb
|
427
430
|
- lib/good_job/dependencies.rb
|
428
431
|
- lib/good_job/engine.rb
|
429
|
-
- lib/good_job/enqueuing.rb
|
430
432
|
- lib/good_job/job_performer.rb
|
431
433
|
- lib/good_job/log_subscriber.rb
|
432
434
|
- lib/good_job/multi_scheduler.rb
|
data/lib/good_job/enqueuing.rb
DELETED