good_job 4.4.2 → 4.5.0

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: 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