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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b5dad621ffb6df8e754cc906eb748605d14525e7286f163f536c577d70f287c
4
- data.tar.gz: 2f2973f6e21b7cf55ceb569cfff1bbf9fa1a579e544c7b96cae15c56cb890ce4
3
+ metadata.gz: 38942c9c17765023398fe2a5c2f3dc2c0ebadc799858ed1102bc47bd11d70ed9
4
+ data.tar.gz: 1dbbff4895a344d948592ec8801e1a325f48b984b66738efdbf785ca4f46f9b6
5
5
  SHA512:
6
- metadata.gz: dbc0e6fe97519eabaee2f82f0de69cfbce24a928ba579ac57fd1b0c38b9693269a7b7bb69042d621a84fca224f2ec335bf9a5c2c7420a9e3e4cf9bbdf0f34460
7
- data.tar.gz: b237d93f4741900bcc7263a754155328d31452cc040ee047d5bdca1dc0d0dbd9dfa416563dd70482d3d56f0bc73ef7d57b91a256760895a8ed9127cef2fb8127
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 (coming soon!)
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' => base_query.scheduled.count,
7
- 'retried' => base_query.retried.count,
8
- 'queued' => base_query.queued.count,
9
- 'running' => base_query.running.count,
10
- 'finished' => base_query.finished.count,
11
- 'discarded' => base_query.discarded.count,
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(params[:job_class]) if params[:job_class].present?
19
- query = query.where(queue_name: params[:queue_name]) if params[:queue_name].present?
20
- query = query.search_text(params[:query]) if params[:query].present?
21
- query = query.where(cron_key: params[:cron_key]) if params[:cron_key].present?
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 params[:state]
24
- case params[:state]
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.id), method: :post, class: "btn btn-sm btn-outline-primary", form_class: "d-inline-block", aria: { label: "Run cron entry now" }, title: "Run cron entry now", data: { confirm: "Confirm run cron entry now" } do %>
41
- <%= render "good_job/shared/icons/play" %>
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
- <ul class="nav nav-tabs bg-light px-3 mb-3">
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", url_for(state: nil), class: "nav-link #{"active" unless params[:state]}" %>
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 url_for({state: name}), class: "nav-link #{"active" if params[:state] == name}" do %>
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
@@ -18,6 +18,8 @@ GoodJob::Engine.routes.draw do
18
18
  resources :cron_entries, only: %i[index show], param: :cron_key do
19
19
  member do
20
20
  post :enqueue
21
+ put :enable
22
+ put :disable
21
23
  end
22
24
  end
23
25
 
@@ -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
@@ -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
- caller.grep(%r{config.ru}).any? || # EXAMPLE: config.ru:3:in `block in <main>' OR config.ru:3:in `new_from_string'
153
- caller.grep(%{/rack/handler/}).any? || # EXAMPLE: iodine-0.7.44/lib/rack/handler/iodine.rb:13:in `start'
154
- (Concurrent.on_jruby? && caller.grep(%r{jruby/rack/rails_booter}).any?) # EXAMPLE: uri:classloader:/jruby/rack/rails_booter.rb:83:in `load_environment'
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
@@ -89,7 +89,7 @@ module GoodJob # :nodoc:
89
89
  thr_scheduler.create_task(thr_cron_entry)
90
90
 
91
91
  Rails.application.executor.wrap do
92
- cron_entry.enqueue(thr_cron_at)
92
+ cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
93
93
  end
94
94
  end
95
95
 
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '3.3.2'
4
+ VERSION = '3.4.1'
5
5
  end
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.3.2
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-07-27 00:00:00.000000000 Z
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
@@ -1,4 +0,0 @@
1
- module GoodJob
2
- module Enqueing
3
- end
4
- end