good_job 4.4.2 → 4.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7a4560e3e97c78d26ea5045bbd6de6d378191a52873be4f213d886f80bdf29c
4
- data.tar.gz: 98cc8a6958a69898966efe296339bfc22130c75d6700ad80934e60948f864861
3
+ metadata.gz: 3f0a8926625f33b36c56def63a1507cfb0f8981a876449e907396aa2338b0b78
4
+ data.tar.gz: ad45bd8133d5f68bcf190bac3f7656864e10df2e49be477024e9f0c8422ce222
5
5
  SHA512:
6
- metadata.gz: 1e1479d596427c82a6ed2b683aa0e8e171e20ab52b3591b49e757efd54b9ad5651c6a10643a42e3838c1042c2d17d96070d30888ceabff19b809185009c93e88
7
- data.tar.gz: '0784732d866e3954803d23758a320d68adcd6ad9da30413419c514ffa5ea68a40dd3652eabd3847b24563814b2a37a48e1b55f2d8358a940daacbc6353fe8171'
6
+ metadata.gz: 2f2e5c4ac4020dfd7aaed53bfb9b7e7480ca91e0aa7cfcbc385ac60d91a4b08e40397734993ffdffc9d5121f40afab04540d363fef925bf8a30bf987938514ab
7
+ data.tar.gz: 3460da22048d4fad135e12644c043723b2dcba8137a69a3b5bebf3b5df11e1c1a0ed0f94a8924b64cab4d1ce8b76bd55e6090a25c43f23afde41b27d398cf59a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.5.0](https://github.com/bensheldon/good_job/tree/v4.5.0) (2024-11-22)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.4.2...v4.5.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add "Discard Cleaner" page to dashboard UI [\#1538](https://github.com/bensheldon/good_job/pull/1538) ([lucasfcunha](https://github.com/lucasfcunha))
10
+ - Add Process memory usage and fix process state update [\#1516](https://github.com/bensheldon/good_job/pull/1516) ([noma4i](https://github.com/noma4i))
11
+
12
+ **Fixed bugs:**
13
+
14
+ - Fix cron double-enqueue because delay close to 0.01 and possibly clock-drift [\#1543](https://github.com/bensheldon/good_job/pull/1543) ([ccouton](https://github.com/ccouton))
15
+ - Fix badge color for running jobs [\#1525](https://github.com/bensheldon/good_job/pull/1525) ([Wittiest](https://github.com/Wittiest))
16
+
17
+ **Closed issues:**
18
+
19
+ - Can't load Dashboard [\#1532](https://github.com/bensheldon/good_job/issues/1532)
20
+ - Should we clean up batches if discarded callback jobs exist? [\#1528](https://github.com/bensheldon/good_job/issues/1528)
21
+ - Modify error color for Running tab when a job has 1 attempt [\#1518](https://github.com/bensheldon/good_job/issues/1518)
22
+ - Silence development warning output [\#1509](https://github.com/bensheldon/good_job/issues/1509)
23
+ - Proposal - A better way of managing errors through the GoodJob UI [\#1464](https://github.com/bensheldon/good_job/issues/1464)
24
+
25
+ **Merged pull requests:**
26
+
27
+ - Ignore some warnings with the `warning` gem [\#1545](https://github.com/bensheldon/good_job/pull/1545) ([Earlopain](https://github.com/Earlopain))
28
+ - Remove unneeded include of pg\_locks in query when displaying jobs table [\#1541](https://github.com/bensheldon/good_job/pull/1541) ([jgrau](https://github.com/jgrau))
29
+ - Update development environment to Rails 8 [\#1539](https://github.com/bensheldon/good_job/pull/1539) ([bensheldon](https://github.com/bensheldon))
30
+ - Bump the bundler-dependencies group with 9 updates [\#1534](https://github.com/bensheldon/good_job/pull/1534) ([dependabot[bot]](https://github.com/apps/dependabot))
31
+ - Bump the bundler-lint group with 5 updates [\#1533](https://github.com/bensheldon/good_job/pull/1533) ([dependabot[bot]](https://github.com/apps/dependabot))
32
+ - Bump rexml from 3.3.8 to 3.3.9 [\#1530](https://github.com/bensheldon/good_job/pull/1530) ([dependabot[bot]](https://github.com/apps/dependabot))
33
+ - Deprecate GoodJob::Job\#recent\_error [\#1526](https://github.com/bensheldon/good_job/pull/1526) ([Wittiest](https://github.com/Wittiest))
34
+
3
35
  ## [v4.4.2](https://github.com/bensheldon/good_job/tree/v4.4.2) (2024-10-18)
4
36
 
5
37
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.4.1...v4.4.2)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ class CleanerController < ApplicationController
5
+ def index
6
+ @filter = JobsFilter.new(params)
7
+
8
+ @discarded_jobs_grouped_by_exception =
9
+ GoodJob::Job.discarded
10
+ .select(<<-SQL.squish)
11
+ SPLIT_PART(error, ': ', 1) AS exception_class,
12
+ count(id) AS failed,
13
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '1 HOUR') AS last_1_hour,
14
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 HOURS') AS last_3_hours,
15
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '24 HOURS') AS last_24_hours,
16
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 DAYS') AS last_3_days,
17
+ COUNT(id) FILTER (WHERE "finished_at" > NOW() - INTERVAL '7 DAYS') AS last_7_days
18
+ SQL
19
+ .order(:exception_class)
20
+ .group(:exception_class)
21
+
22
+ @discarded_jobs_grouped_by_class =
23
+ GoodJob::Job.discarded
24
+ .select(<<-SQL.squish)
25
+ job_class,
26
+ count(id) AS failed,
27
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '1 HOUR') AS last_1_hour,
28
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 HOURS') AS last_3_hours,
29
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '24 HOURS') AS last_24_hours,
30
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '3 DAYS') AS last_3_days,
31
+ COUNT(*) FILTER (WHERE "finished_at" > NOW() - INTERVAL '7 DAYS') AS last_7_days
32
+ SQL
33
+ .order(:job_class)
34
+ .group(:job_class)
35
+ end
36
+ end
37
+ end
@@ -7,12 +7,14 @@ module GoodJob
7
7
  batches_count = GoodJob::BatchRecord.all.size
8
8
  cron_entries_count = GoodJob::CronEntry.all.size
9
9
  processes_count = GoodJob::Process.active.count
10
+ discarded_count = GoodJob::Job.discarded.count
10
11
 
11
12
  render json: {
12
13
  jobs_count: helpers.number_to_human(jobs_count),
13
14
  batches_count: helpers.number_to_human(batches_count),
14
15
  cron_entries_count: helpers.number_to_human(cron_entries_count),
15
16
  processes_count: helpers.number_to_human(processes_count),
17
+ discarded_count: helpers.number_to_human(discarded_count),
16
18
  }
17
19
  end
18
20
 
@@ -54,6 +54,7 @@ module GoodJob
54
54
  query: params[:query],
55
55
  state: params[:state],
56
56
  cron_key: params[:cron_key],
57
+ finished_since: params[:finished_since],
57
58
  }.merge(override).delete_if { |_, v| v.blank? }
58
59
  end
59
60
 
@@ -27,6 +27,7 @@ module GoodJob
27
27
  query = query.where(queue_name: filter_params[:queue_name]) if filter_params[:queue_name].present?
28
28
  query = query.search_text(filter_params[:query]) if filter_params[:query].present?
29
29
  query = query.where(cron_key: filter_params[:cron_key]) if filter_params[:cron_key].present?
30
+ query = query.where(finished_at: finished_since(filter_params[:finished_since])..) if filter_params[:finished_since].present?
30
31
 
31
32
  if filter_params[:state]
32
33
  case filter_params[:state]
@@ -39,7 +40,7 @@ module GoodJob
39
40
  when 'scheduled'
40
41
  query = query.scheduled
41
42
  when 'running'
42
- query = query.running.select("#{GoodJob::Job.table_name}.*", 'pg_locks.locktype')
43
+ query = query.running
43
44
  when 'queued'
44
45
  query = query.queued
45
46
  end
@@ -55,11 +56,26 @@ module GoodJob
55
56
  private
56
57
 
57
58
  def query_for_records
58
- filtered_query.includes_advisory_locks
59
+ filtered_query
59
60
  end
60
61
 
61
62
  def default_base_query
62
63
  GoodJob::Job.all
63
64
  end
65
+
66
+ def finished_since(finished_since)
67
+ case finished_since
68
+ when '1_hour_ago'
69
+ 1.hour.ago
70
+ when '3_hours_ago'
71
+ 3.hours.ago
72
+ when '24_hours_ago'
73
+ 24.hours.ago
74
+ when '3_days_ago'
75
+ 3.days.ago
76
+ when '7_days_ago'
77
+ 7.days.ago
78
+ end
79
+ end
64
80
  end
65
81
  end
@@ -390,7 +390,12 @@ module GoodJob
390
390
  # If the job has been retried, the error will be fetched from the previous {Execution} record.
391
391
  # @return [String]
392
392
  def recent_error
393
- error || executions[-2]&.error
393
+ GoodJob.deprecator.warn(<<~DEPRECATION)
394
+ The `GoodJob::Job#recent_error` method is deprecated and will be removed in the next major release.
395
+
396
+ Replace usage of GoodJob::Job#recent_error with `GoodJob::Job#error`.
397
+ DEPRECATION
398
+ error
394
399
  end
395
400
 
396
401
  # Errors for the job to be displayed in the Dashboard.
@@ -12,6 +12,28 @@ module GoodJob # :nodoc:
12
12
  STALE_INTERVAL = 30.seconds
13
13
  # Interval until the process record is treated as expired
14
14
  EXPIRED_INTERVAL = 5.minutes
15
+ PROCESS_MEMORY = case RUBY_PLATFORM
16
+ when /linux/
17
+ lambda do |pid|
18
+ File.readlines("/proc/#{pid}/smaps_rollup").each do |line|
19
+ next unless line.start_with?('Pss:')
20
+
21
+ break line.split[1].to_i
22
+ end
23
+ rescue Errno::ENOENT
24
+ File.readlines("/proc/#{pid}/status").each do |line|
25
+ next unless line.start_with?('VmRSS:')
26
+
27
+ break line.split[1].to_i
28
+ end
29
+ end
30
+ when /darwin|bsd/
31
+ lambda do |pid|
32
+ `ps -o pid,rss -p #{pid.to_i}`.lines.last.split.last.to_i
33
+ end
34
+ else
35
+ ->(_pid) { 0 }
36
+ end
15
37
 
16
38
  self.table_name = 'good_job_processes'
17
39
  self.implicit_order_column = 'created_at'
@@ -56,6 +78,13 @@ module GoodJob # :nodoc:
56
78
  end
57
79
  end
58
80
 
81
+ # @return [Integer]
82
+ def self.memory_usage(pid)
83
+ PROCESS_MEMORY.call(pid)
84
+ rescue StandardError
85
+ 0
86
+ end
87
+
59
88
  def self.find_or_create_record(id:, with_advisory_lock: false)
60
89
  attributes = {
61
90
  id: id,
@@ -83,6 +112,7 @@ module GoodJob # :nodoc:
83
112
  {
84
113
  hostname: Socket.gethostname,
85
114
  pid: ::Process.pid,
115
+ memory: memory_usage(::Process.pid),
86
116
  proctitle: $PROGRAM_NAME,
87
117
  preserve_job_records: GoodJob.preserve_job_records,
88
118
  retry_on_unhandled_error: GoodJob.retry_on_unhandled_error,
@@ -98,8 +128,8 @@ module GoodJob # :nodoc:
98
128
  end
99
129
 
100
130
  def refresh
101
- self.state = self.class.process_state
102
131
  reload # verify the record still exists in the database
132
+ self.state = self.class.process_state
103
133
  update(state: state, updated_at: Time.current)
104
134
  rescue ActiveRecord::RecordNotFound
105
135
  @new_record = true
@@ -35,17 +35,18 @@
35
35
  </div>
36
36
  <div class="col-4 col-lg-1 text-lg-end">
37
37
  <div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.attempts" %></div>
38
- <% if job.executions_count > 0 && job.status != :finished %>
38
+ <% if job.error %>
39
39
  <%= tag.span job.executions_count, class: "badge rounded-pill bg-danger",
40
40
  data: {
41
41
  bs_toggle: "popover",
42
42
  bs_trigger: "hover focus click",
43
43
  bs_placement: "bottom",
44
- bs_content: job.recent_error,
44
+ bs_content: job.display_error,
45
45
  }
46
46
  %>
47
47
  <% else %>
48
- <span class="badge bg-secondary rounded-pill"><%= job.executions_count %></span>
48
+ <% executions_badge_color = job.executions_count > 1 ? "bg-warning" : "bg-secondary" %>
49
+ <span class="badge rounded-pill <%= executions_badge_color %>"><%= job.executions_count %></span>
49
50
  <% end %>
50
51
  </div>
51
52
  <div class="mt-3 mt-lg-0 col d-flex gap-3 align-items-center justify-content-end">
@@ -79,7 +80,7 @@
79
80
  <% end %>
80
81
  </li>
81
82
  <li>
82
- <%= link_to job_path(job.id), method: :delete, class: "dropdown-item #{'disabled' unless job.status.in? [:discarded, :finished]}", title: t(".actions.destroy"), data: { confirm: t(".actions.confirm_destroy"), disable: true } do %>
83
+ <%= link_to job_path(job.id), method: :delete, class: "dropdown-item #{'disabled' unless job.finished?}", title: t(".actions.destroy"), data: { confirm: t(".actions.confirm_destroy"), disable: true } do %>
83
84
  <%= render_icon "trash" %>
84
85
  <%= t "good_job.actions.destroy" %>
85
86
  <% end %>
@@ -0,0 +1,85 @@
1
+ <div class="border-bottom">
2
+ <h2 class="pt-3 pb-2"><%= t ".title" %></h2>
3
+ </div>
4
+
5
+ <div>
6
+ <h4 class="pt-3 pb-2"><%= t ".grouped_by_class" %></h4>
7
+ <table class="table align-middle" id="by-job-class">
8
+ <thead>
9
+ <tr>
10
+ <th scope="col" class="col-3"><%= t ".class" %></th>
11
+ <th scope="col" class="text-center"><%= t ".all" %></th>
12
+ <th scope="col" class="text-center"><%= t ".last_1_hour" %></th>
13
+ <th scope="col" class="text-center"><%= t ".last_3_hours" %></th>
14
+ <th scope="col" class="text-center"><%= t ".last_24_hours" %></th>
15
+ <th scope="col" class="text-center"><%= t ".last_3_days" %></th>
16
+ <th scope="col" class="text-center"><%= t ".last_7_days" %></th>
17
+ </tr>
18
+ </thead>
19
+ <tbody>
20
+ <% @discarded_jobs_grouped_by_class.each do |discard_job| %>
21
+ <tr>
22
+ <td scope="row" class="col-3 text-break"><%= discard_job.job_class %></td>
23
+ <td class="text-center "><%= link_to discard_job.failed, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded')) %></td>
24
+ <td class="text-center"><%= link_to discard_job.last_1_hour, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '1_hour_ago')) %></td>
25
+ <td class="text-center"><%= link_to discard_job.last_3_hours, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '3_hours_ago')) %></td>
26
+ <td class="text-center"><%= link_to discard_job.last_24_hours, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '24_hours_ago')) %></td>
27
+ <td class="text-center"><%= link_to discard_job.last_3_days, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '3_days_ago')) %></td>
28
+ <td class="text-center"><%= link_to discard_job.last_7_days, jobs_path(@filter.to_params(job_class: discard_job.job_class, state: 'discarded', finished_since: '7_days_ago')) %></td>
29
+ </tr>
30
+ <% end %>
31
+ </tbody>
32
+ <tfoot>
33
+ <tr>
34
+ <td scope="row" class="col-3 fw-bold"><%= t ".total" %></td>
35
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:failed), jobs_path(@filter.to_params(state: 'discarded')) %></td>
36
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_1_hour), jobs_path(@filter.to_params(state: 'discarded', finished_since: '1_hour_ago')) %></td>
37
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_3_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_hours_ago')) %></td>
38
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_24_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '24_hours_ago')) %></td>
39
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_3_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_days_ago')) %></td>
40
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_class.sum(&:last_7_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '7_days_ago')) %></td>
41
+ </tr>
42
+ </tfoot>
43
+ </table>
44
+ </div>
45
+
46
+ <div>
47
+ <h4 class="pt-3 pb-2"><%= t ".grouped_by_exception" %></h4>
48
+ <table class="table align-middle" id="by-exception">
49
+ <thead>
50
+ <tr>
51
+ <th scope="col" class="col-3"><%= t ".exception" %></th>
52
+ <th scope="col" class="text-center"><%= t ".all" %></th>
53
+ <th scope="col" class="text-center"><%= t ".last_1_hour" %></th>
54
+ <th scope="col" class="text-center"><%= t ".last_3_hours" %></th>
55
+ <th scope="col" class="text-center"><%= t ".last_24_hours" %></th>
56
+ <th scope="col" class="text-center"><%= t ".last_3_days" %></th>
57
+ <th scope="col" class="text-center"><%= t ".last_7_days" %></th>
58
+ </tr>
59
+ </thead>
60
+ <tbody>
61
+ <% @discarded_jobs_grouped_by_exception.each do |discard_job| %>
62
+ <tr>
63
+ <td scope="row" class="col-3 text-break"><%= discard_job.exception_class %></td>
64
+ <td class="text-center"><%= link_to discard_job.failed, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class)) %></td>
65
+ <td class="text-center"><%= link_to discard_job.last_1_hour, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '1_hour_ago')) %></td>
66
+ <td class="text-center"><%= link_to discard_job.last_3_hours, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '3_hours_ago')) %></td>
67
+ <td class="text-center"><%= link_to discard_job.last_24_hours, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '24_hours_ago')) %></td>
68
+ <td class="text-center"><%= link_to discard_job.last_3_days, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '3_days_ago')) %></td>
69
+ <td class="text-center"><%= link_to discard_job.last_7_days, jobs_path(@filter.to_params(state: 'discarded', query: discard_job.exception_class, finished_since: '7_days_ago')) %></td>
70
+ </tr>
71
+ <% end %>
72
+ </tbody>
73
+ <tfoot>
74
+ <tr>
75
+ <td scope="row" class="col-3 fw-bold"><%= t ".total" %></td>
76
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:failed), jobs_path(@filter.to_params(state: 'discarded')) %></td>
77
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_1_hour), jobs_path(@filter.to_params(state: 'discarded', finished_since: '1_hour_ago')) %></td>
78
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_3_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_hours_ago')) %></td>
79
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_24_hours), jobs_path(@filter.to_params(state: 'discarded', finished_since: '24_hours_ago')) %></td>
80
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_3_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '3_days_ago')) %></td>
81
+ <td class="text-center fw-bold"><%= link_to @discarded_jobs_grouped_by_exception.sum(&:last_7_days), jobs_path(@filter.to_params(state: 'discarded', finished_since: '7_days_ago')) %></td>
82
+ </tr>
83
+ </tfoot>
84
+ </table>
85
+ </div>
@@ -84,7 +84,7 @@
84
84
  </div>
85
85
  <div class="col-4 col-lg-1 text-lg-end">
86
86
  <div class="d-lg-none small text-muted mt-1"><%= t "good_job.models.job.attempts" %></div>
87
- <% if job.executions_count > 0 && job.status != :succeeded %>
87
+ <% if job.error %>
88
88
  <%= tag.span job.executions_count, class: "badge rounded-pill bg-danger",
89
89
  data: {
90
90
  bs_toggle: "popover",
@@ -94,7 +94,8 @@
94
94
  }
95
95
  %>
96
96
  <% else %>
97
- <span class="badge bg-secondary rounded-pill"><%= job.executions_count %></span>
97
+ <% executions_badge_color = job.executions_count > 1 ? "bg-warning" : "bg-secondary" %>
98
+ <span class="badge rounded-pill <%= executions_badge_color %>"><%= job.executions_count %></span>
98
99
  <% end %>
99
100
  </div>
100
101
  <div class="mt-3 mt-lg-0 col">
@@ -38,6 +38,7 @@
38
38
  <span class="badge rounded-pill bg-body-secondary text-secondary"><%= process.state["pid"] %></span>
39
39
  <span class="text-muted small">@</span>
40
40
  <span class="badge rounded-pill bg-body-secondary text-secondary"><%= process.state["hostname"] %></span>
41
+ <span class="badge rounded-pill bg-body-secondary text-secondary"><%= (process.state["memory"] / 1024).to_i %> MB</span>
41
42
  </div>
42
43
  </div>
43
44
  <div class="col">
@@ -44,6 +44,12 @@
44
44
  <%= t(".performance") %>
45
45
  <% end %>
46
46
  </li>
47
+ <li class="nav-item">
48
+ <%= link_to cleaner_index_path, class: ["nav-link", ("active" if controller_name == 'cleaner')] do %>
49
+ <%= t(".cleaner") %>
50
+ <span data-async-values-target="value" data-async-values-key="discarded_count" class="badge bg-secondary rounded-pill d-none"></span>
51
+ <% end %>
52
+ </li>
47
53
  </ul>
48
54
 
49
55
  <ul class="navbar-nav">
@@ -53,7 +53,7 @@
53
53
  "check_name": "SQL",
54
54
  "message": "Possible SQL injection",
55
55
  "file": "app/models/good_job/job.rb",
56
- "line": 140,
56
+ "line": 135,
57
57
  "link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
58
58
  "code": "Arel.sql(\"(CASE #{queues.map.with_index do\n sanitize_sql_array([\"WHEN queue_name = ? THEN ?\", queue_name, index])\n end.join(\" \")} ELSE #{queues.size} END)\")",
59
59
  "render_path": null,
@@ -68,8 +68,31 @@
68
68
  89
69
69
  ],
70
70
  "note": "Developer provided value, queue_name, is sanitized."
71
+ },
72
+ {
73
+ "warning_type": "Command Injection",
74
+ "warning_code": 14,
75
+ "fingerprint": "dbb80167f57edebfd9da72e1278d425095a0329755e24d3c50e0fda6bb21c097",
76
+ "check_name": "Execute",
77
+ "message": "Possible command injection",
78
+ "file": "app/models/good_job/process.rb",
79
+ "line": 32,
80
+ "link": "https://brakemanscanner.org/docs/warning_types/command_injection/",
81
+ "code": "`ps -o pid,rss -p #{pid.to_i}`",
82
+ "render_path": null,
83
+ "location": {
84
+ "type": "method",
85
+ "class": "Process",
86
+ "method": null
87
+ },
88
+ "user_input": "pid.to_i",
89
+ "confidence": "Medium",
90
+ "cwe_id": [
91
+ 77
92
+ ],
93
+ "note": ""
71
94
  }
72
95
  ],
73
- "updated": "2024-07-18 18:05:56 -0700",
74
- "brakeman_version": "6.1.2"
96
+ "updated": "2024-11-16 17:00:20 -0600",
97
+ "brakeman_version": "6.2.1"
75
98
  }
@@ -35,6 +35,20 @@ de:
35
35
  callback_jobs: Callback-Jobs
36
36
  table:
37
37
  no_batches_found: Keine Batches gefunden.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Bist du sicher, dass du diesen Cron-Eintrag deaktivieren willst?
@@ -236,6 +250,7 @@ de:
236
250
  search: Suchen
237
251
  navbar:
238
252
  batches: Batches
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Jobs
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ en:
35
35
  callback_jobs: Callback Jobs
36
36
  table:
37
37
  no_batches_found: No batches found.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Are you sure you want to disable this cron entry?
@@ -236,6 +250,7 @@ en:
236
250
  search: Search
237
251
  navbar:
238
252
  batches: Batches
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Jobs
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ es:
35
35
  callback_jobs: Callback Jobs
36
36
  table:
37
37
  no_batches_found: No hay lotes.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: "¿Estás seguro que querés deshabilitar esta tarea programada?"
@@ -236,6 +250,7 @@ es:
236
250
  search: Buscar
237
251
  navbar:
238
252
  batches: Lotes
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Tareas
241
256
  live_poll: En vivo
@@ -35,6 +35,20 @@ fr:
35
35
  callback_jobs: Jobs de rappel
36
36
  table:
37
37
  no_batches_found: Aucun lot trouvé.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Voulez-vous vraiment désactiver cette entrée cron ?
@@ -236,6 +250,7 @@ fr:
236
250
  search: Rechercher
237
251
  navbar:
238
252
  batches: Lots
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Jobs
241
256
  live_poll: En direct
@@ -35,6 +35,20 @@ it:
35
35
  callback_jobs: Job di callback
36
36
  table:
37
37
  no_batches_found: Nessun batch trovato.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Sei sicuro di voler disabilitare questa voce cron?
@@ -236,6 +250,7 @@ it:
236
250
  search: Cerca
237
251
  navbar:
238
252
  batches: Batch
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Job
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ ja:
35
35
  callback_jobs: コールバックジョブ
36
36
  table:
37
37
  no_batches_found: バッチが見つかりませんでした。
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: このcronエントリを無効化してもよろしいですか?
@@ -236,6 +250,7 @@ ja:
236
250
  search: 検索
237
251
  navbar:
238
252
  batches: バッチ
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: ジョブ
241
256
  live_poll: リアルタイム更新
@@ -35,6 +35,20 @@ ko:
35
35
  callback_jobs: 콜백 작업
36
36
  table:
37
37
  no_batches_found: 배치를 찾을 수 없습니다.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: 이 cron 엔트리를 비활성화하시겠습니까?
@@ -236,6 +250,7 @@ ko:
236
250
  search: 검색
237
251
  navbar:
238
252
  batches: 배치
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: 작업
241
256
  live_poll: 실시간 업데이트
@@ -35,6 +35,20 @@ nl:
35
35
  callback_jobs: Terugbelopdrachten
36
36
  table:
37
37
  no_batches_found: Geen batches gevonden.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Weet u zekerf dat u deze cron-vermelding wilt uitschakelen?
@@ -236,6 +250,7 @@ nl:
236
250
  search: Zoekopdracht
237
251
  navbar:
238
252
  batches: Batches
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: Taken
241
256
  live_poll: Live Poll
@@ -35,6 +35,20 @@ pt-BR:
35
35
  callback_jobs: Tarefas de Callback
36
36
  table:
37
37
  no_batches_found: Nenhum lote encontrado.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Tem certeza de que deseja desativar esta tarefa programada?
@@ -236,6 +250,7 @@ pt-BR:
236
250
  search: Pesquisar
237
251
  navbar:
238
252
  batches: Lotes
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Agendamentos
240
255
  jobs: Tarefas
241
256
  live_poll: Acompanhamento ao Vivo
@@ -35,6 +35,20 @@ ru:
35
35
  callback_jobs: Коллбеки
36
36
  table:
37
37
  no_batches_found: Группы заданий не найдены.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Вы уверены, что хотите отключить эту задачу cron?
@@ -262,6 +276,7 @@ ru:
262
276
  search: Поиск
263
277
  navbar:
264
278
  batches: Пакетные задания
279
+ cleaner: Discard Cleaner
265
280
  cron_schedules: Cron
266
281
  jobs: Задания
267
282
  live_poll: Обновления в реальном времени
@@ -35,6 +35,20 @@ tr:
35
35
  callback_jobs: Geri Çağırma İşleri
36
36
  table:
37
37
  no_batches_found: Toplu İşlem bulunamadı.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Bu cron girişini devre dışı bırakmak istediğinizden emin misiniz?
@@ -236,6 +250,7 @@ tr:
236
250
  search: Ara
237
251
  navbar:
238
252
  batches: Toplu İşler
253
+ cleaner: Discard Cleaner
239
254
  cron_schedules: Cron
240
255
  jobs: İşler
241
256
  live_poll: Anlık Güncelleme
@@ -35,6 +35,20 @@ uk:
35
35
  callback_jobs: Задачі з зворотнім викликом
36
36
  table:
37
37
  no_batches_found: Пакети не знайдені.
38
+ cleaner:
39
+ index:
40
+ all: All
41
+ class: Class
42
+ exception: Exception
43
+ grouped_by_class: Discards grouped by Class
44
+ grouped_by_exception: Discards grouped by Exception
45
+ last_1_hour: Last 1 hour
46
+ last_24_hours: Last 24 hours
47
+ last_3_days: Last 3 days
48
+ last_3_hours: Last 3 hours
49
+ last_7_days: Last 7 days
50
+ title: Discard Cleaner
51
+ total: Total
38
52
  cron_entries:
39
53
  actions:
40
54
  confirm_disable: Ви впевнені, що хочете вимкнути цю cron-запис?
@@ -262,6 +276,7 @@ uk:
262
276
  search: Пошук
263
277
  navbar:
264
278
  batches: Пакети
279
+ cleaner: Discard Cleaner
265
280
  cron_schedules: Cron
266
281
  jobs: Задачі
267
282
  live_poll: Живе Опитування
data/config/routes.rb CHANGED
@@ -34,8 +34,8 @@ GoodJob::Engine.routes.draw do
34
34
  end
35
35
 
36
36
  resources :processes, only: %i[index]
37
-
38
37
  resources :performance, only: %i[index show]
38
+ resources :cleaner, only: %i[index]
39
39
 
40
40
  scope :frontend, controller: :frontends, defaults: { version: GoodJob::VERSION.tr(".", "-") } do
41
41
  get "modules/:version/:id", action: :module, as: :frontend_module, constraints: { format: 'js' }
@@ -144,7 +144,7 @@ module GoodJob # :nodoc:
144
144
  # Tests whether an active advisory lock has been taken on the record.
145
145
  # @return [Boolean]
146
146
  def advisory_locked?
147
- @advisory_locked_connection&.weakref_alive? && @advisory_locked_connection&.active?
147
+ @advisory_locked_connection&.weakref_alive? && @advisory_locked_connection.active?
148
148
  end
149
149
 
150
150
  # @!visibility private
@@ -26,11 +26,13 @@ module GoodJob # :nodoc:
26
26
  end
27
27
 
28
28
  # Execution configuration to be scheduled
29
- # @return [Hash]
29
+ # @return [Array<CronEntry>]
30
30
  attr_reader :cron_entries
31
31
 
32
32
  # @param cron_entries [Array<CronEntry>]
33
33
  # @param start_on_initialize [Boolean]
34
+ # @param graceful_restart_period [ActiveSupport::Duration, nil]
35
+ # @param executor [Concurrent::Executor]
34
36
  def initialize(cron_entries = [], start_on_initialize: false, graceful_restart_period: nil, executor: Concurrent.global_io_executor)
35
37
  @executor = executor
36
38
  @running = false
@@ -82,16 +84,26 @@ module GoodJob # :nodoc:
82
84
 
83
85
  # Enqueues a scheduled task
84
86
  # @param cron_entry [CronEntry] the CronEntry object to schedule
85
- # @param previously_at [Date, Time, ActiveSupport::TimeWithZone, nil] the last, +in-memory+, scheduled time the cron task was intended to run
86
- def create_task(cron_entry, previously_at: nil)
87
- cron_at = cron_entry.next_at(previously_at: previously_at)
88
- delay = [(cron_at - Time.current).to_f, 0].max
89
- future = Concurrent::ScheduledTask.new(delay, args: [self, cron_entry, cron_at], executor: @executor) do |thr_scheduler, thr_cron_entry, thr_cron_at|
90
- # Re-schedule the next cron task before executing the current task
91
- thr_scheduler.create_task(thr_cron_entry, previously_at: thr_cron_at)
92
-
93
- Rails.application.executor.wrap do
94
- cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
87
+ # @param at [Time, nil] When a task needs to optionally be rescheduled because of clock-drift or other inaccuracy
88
+ # @param previously_at [Time, nil] the last +in-memory+ scheduled time the cron task was intended to run
89
+ def create_task(cron_entry, at: nil, previously_at: nil)
90
+ cron_at = at || cron_entry.next_at(previously_at: previously_at)
91
+
92
+ # ScheduledTask runs immediately if delay is <= 0.01; avoid ever scheduling the task before the intended time
93
+ # https://github.com/ruby-concurrency/concurrent-ruby/blob/56227a4c3ebdd53b8b0976eb8296ceb7a093496f/lib/concurrent-ruby/concurrent/executor/timer_set.rb#L97
94
+ delay = cron_at <= Time.current ? 0.0 : [(cron_at - Time.current).to_f, 0.02].max
95
+
96
+ future = Concurrent::ScheduledTask.new(delay, args: [self, cron_entry, cron_at, previously_at], executor: @executor) do |thr_manager, thr_cron_entry, thr_cron_at|
97
+ if thr_cron_at && thr_cron_at > Time.current
98
+ # If clock drift or other inaccuracy, reschedule the task again
99
+ thr_manager.create_task(thr_cron_entry, at: thr_cron_at, previously_at: previously_at)
100
+ else
101
+ # Re-schedule the next cron task before executing the current task
102
+ thr_manager.create_task(thr_cron_entry, previously_at: thr_cron_at)
103
+
104
+ Rails.application.executor.wrap do
105
+ cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
106
+ end
95
107
  end
96
108
  end
97
109
 
@@ -108,7 +120,7 @@ module GoodJob # :nodoc:
108
120
 
109
121
  time_period = @graceful_restart_period.ago..Time.current
110
122
  cron_entry.within(time_period).each do |cron_at|
111
- future = Concurrent::Future.new(args: [self, cron_entry, cron_at], executor: @executor) do |_thr_scheduler, thr_cron_entry, thr_cron_at|
123
+ future = Concurrent::Future.new(args: [self, cron_entry, cron_at], executor: @executor) do |_thr_manager, thr_cron_entry, thr_cron_at|
112
124
  Rails.application.executor.wrap do
113
125
  cron_entry.enqueue(thr_cron_at) if thr_cron_entry.enabled?
114
126
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '4.4.2'
5
+ VERSION = '4.5.0'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
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: 4.4.2
4
+ version: 4.5.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: 2024-10-18 00:00:00.000000000 Z
11
+ date: 2024-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -254,6 +254,7 @@ files:
254
254
  - app/charts/good_job/scheduled_by_queue_chart.rb
255
255
  - app/controllers/good_job/application_controller.rb
256
256
  - app/controllers/good_job/batches_controller.rb
257
+ - app/controllers/good_job/cleaner_controller.rb
257
258
  - app/controllers/good_job/cron_entries_controller.rb
258
259
  - app/controllers/good_job/frontends_controller.rb
259
260
  - app/controllers/good_job/jobs_controller.rb
@@ -305,6 +306,7 @@ files:
305
306
  - app/views/good_job/batches/_table.erb
306
307
  - app/views/good_job/batches/index.html.erb
307
308
  - app/views/good_job/batches/show.html.erb
309
+ - app/views/good_job/cleaner/index.html.erb
308
310
  - app/views/good_job/cron_entries/index.html.erb
309
311
  - app/views/good_job/cron_entries/show.html.erb
310
312
  - app/views/good_job/jobs/_executions.erb