good_job 3.3.3 → 3.4.2
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 +55 -3
- 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 +17 -1
- 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/configuration.rb +28 -0
- data/lib/good_job/cron_manager.rb +1 -1
- data/lib/good_job/log_subscriber.rb +4 -4
- 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: 6c31e9dc63de3bff82d55ba85e17690ad52dcf2d9b8ebc8bcb0d8dc7eab261b0
|
|
4
|
+
data.tar.gz: bab18a8d7b7328f172a6b66adba1767281cba281a8f38e76c8bfd303fd6a25a9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d93aa0c5246d5e7be37f5887b7b3c8618815b88558360bb351ce9df7529460994506ddbdd84f1c57d4066216ea0fa98afdb937468c60aafd4966a2de88967656
|
|
7
|
+
data.tar.gz: b25da80ce2ca30517c4ec89187e062db7c1fd1e0b16ce7adaec33fb7da53ce1d56163a58595b57b4919d9d9cdbdf81ef6cf61664c48d2940c1f06fc70aaa6589
|
data/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,69 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [v3.
|
|
3
|
+
## [v3.4.2](https://github.com/bensheldon/good_job/tree/v3.4.2) (2022-08-13)
|
|
4
4
|
|
|
5
|
-
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.
|
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.1...v3.4.2)
|
|
6
|
+
|
|
7
|
+
**Fixed bugs:**
|
|
8
|
+
|
|
9
|
+
- Jobs enqueued via dashboard ignores app default\_locale [\#697](https://github.com/bensheldon/good_job/issues/697)
|
|
10
|
+
- Include better exception log messages, including class and backtrace [\#693](https://github.com/bensheldon/good_job/pull/693) ([bensheldon](https://github.com/bensheldon))
|
|
6
11
|
|
|
7
12
|
**Closed issues:**
|
|
8
13
|
|
|
9
|
-
-
|
|
14
|
+
- Do we need to implement concurrency with scheduled cron jobs? [\#690](https://github.com/bensheldon/good_job/issues/690)
|
|
15
|
+
- Uninitialized constant GoodJob::JobsController [\#674](https://github.com/bensheldon/good_job/issues/674)
|
|
16
|
+
- ActiveRecord::StatementInvalid: PG::ConnectionBad: PQsocket\(\) can't get socket descriptor every 30 minutes aprox. [\#579](https://github.com/bensheldon/good_job/issues/579)
|
|
17
|
+
- Handle assets in dashboard when rails app is behind proxy path [\#424](https://github.com/bensheldon/good_job/issues/424)
|
|
18
|
+
|
|
19
|
+
**Merged pull requests:**
|
|
20
|
+
|
|
21
|
+
- Enqueues jobs with I18n default locale [\#698](https://github.com/bensheldon/good_job/pull/698) ([esasse](https://github.com/esasse))
|
|
22
|
+
|
|
23
|
+
## [v3.4.1](https://github.com/bensheldon/good_job/tree/v3.4.1) (2022-08-06)
|
|
24
|
+
|
|
25
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.4.0...v3.4.1)
|
|
26
|
+
|
|
27
|
+
**Closed issues:**
|
|
28
|
+
|
|
29
|
+
- Add `cron_enabled` to Process state [\#673](https://github.com/bensheldon/good_job/issues/673)
|
|
30
|
+
- Good job is using a lot of memory / ram [\#613](https://github.com/bensheldon/good_job/issues/613)
|
|
31
|
+
|
|
32
|
+
**Merged pull requests:**
|
|
33
|
+
|
|
34
|
+
- 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))
|
|
35
|
+
|
|
36
|
+
## [v3.4.0](https://github.com/bensheldon/good_job/tree/v3.4.0) (2022-08-05)
|
|
37
|
+
|
|
38
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.3.3...v3.4.0)
|
|
39
|
+
|
|
40
|
+
**Implemented enhancements:**
|
|
41
|
+
|
|
42
|
+
- 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))
|
|
43
|
+
- Reverse Dashboard Filter Hierarchy to be: queues+jobs then state [\#666](https://github.com/bensheldon/good_job/pull/666) ([bensheldon](https://github.com/bensheldon))
|
|
44
|
+
- 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))
|
|
45
|
+
- 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))
|
|
46
|
+
|
|
47
|
+
**Closed issues:**
|
|
48
|
+
|
|
49
|
+
- Cron Schedule jobs add disable action [\#540](https://github.com/bensheldon/good_job/issues/540)
|
|
10
50
|
|
|
11
51
|
**Merged pull requests:**
|
|
12
52
|
|
|
53
|
+
- 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))
|
|
54
|
+
|
|
55
|
+
## [v3.3.3](https://github.com/bensheldon/good_job/tree/v3.3.3) (2022-08-02)
|
|
56
|
+
|
|
57
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.3.2...v3.3.3)
|
|
58
|
+
|
|
59
|
+
**Fixed bugs:**
|
|
60
|
+
|
|
13
61
|
- Detect usage of `puma` CLI for async mode [\#686](https://github.com/bensheldon/good_job/pull/686) ([bensheldon](https://github.com/bensheldon))
|
|
14
62
|
|
|
63
|
+
**Closed issues:**
|
|
64
|
+
|
|
65
|
+
- Async not working Rails 7 with puma CLI [\#685](https://github.com/bensheldon/good_job/issues/685)
|
|
66
|
+
|
|
15
67
|
## [v3.3.2](https://github.com/bensheldon/good_job/tree/v3.3.2) (2022-07-27)
|
|
16
68
|
|
|
17
69
|
[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,13 +86,29 @@ 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
|
|
92
106
|
current_thread.cron_at = cron_at
|
|
93
107
|
|
|
94
108
|
configured_job = job_class.constantize.set(set_value)
|
|
95
|
-
|
|
109
|
+
I18n.with_locale(I18n.default_locale) do
|
|
110
|
+
kwargs_value.present? ? configured_job.perform_later(*args_value, **kwargs_value) : configured_job.perform_later(*args_value)
|
|
111
|
+
end
|
|
96
112
|
end
|
|
97
113
|
rescue ActiveRecord::RecordNotUnique
|
|
98
114
|
false
|
|
@@ -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
|
|
@@ -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
|
|
@@ -32,7 +32,7 @@ module GoodJob
|
|
|
32
32
|
return unless exception
|
|
33
33
|
|
|
34
34
|
error do
|
|
35
|
-
"GoodJob error: #{exception}\n #{exception.backtrace}"
|
|
35
|
+
"GoodJob error: #{exception.class}: #{exception}\n #{exception.backtrace}"
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -42,7 +42,7 @@ module GoodJob
|
|
|
42
42
|
return unless exception
|
|
43
43
|
|
|
44
44
|
error do
|
|
45
|
-
"GoodJob error: #{exception}\n #{exception.backtrace}"
|
|
45
|
+
"GoodJob error: #{exception.class}: #{exception}\n #{exception.backtrace}"
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -123,10 +123,10 @@ module GoodJob
|
|
|
123
123
|
|
|
124
124
|
# @!macro notification_responder
|
|
125
125
|
def notifier_notify_error(event)
|
|
126
|
-
|
|
126
|
+
exception = event.payload[:error]
|
|
127
127
|
|
|
128
128
|
error do
|
|
129
|
-
"Notifier errored: #{
|
|
129
|
+
"Notifier errored: #{exception.class}: #{exception}\n #{exception.backtrace}"
|
|
130
130
|
end
|
|
131
131
|
end
|
|
132
132
|
|
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.2
|
|
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-08-
|
|
11
|
+
date: 2022-08-13 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