good_job 3.0.2 → 3.1.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: 03dafffb596eaa6c7cf0994a40586039ac38215dcb0942850b1067deec0ac04d
4
- data.tar.gz: 917cbb1baebf9e6f8f7767d54606d0a07b02d3c28971e65369603f71500abf34
3
+ metadata.gz: bc6e3cffa2189a101181b7ceb278de97ded27d14dd6da915ea8fe3729907dd88
4
+ data.tar.gz: 0504d2f2efd09d1d52669597e364f62b69edc739d3c76b8bcfdd1933ea7cb8da
5
5
  SHA512:
6
- metadata.gz: fd0c36fee775613d86ba832c6777eb9c72b651630f24b6236ea1b5373957d045121c3d5a0aea528cded163da638b7b6e41c3b155915186350bd1675e27b3ffee
7
- data.tar.gz: 041f30d08e8128d81ffdbf59e19f958761b809d9993facb5919119346cfc30b2f8da715658be47e94f317ffed3953381a93141a6bdc6540ab3f0b186604970f0
6
+ metadata.gz: c10f675a3635f481e370502828ed0902aff3464e19f161bf5f300463a2671ecac09643eb0943030eda30163171b67986b0f9e31ca423ad5da9e4ffd5b23a7df7
7
+ data.tar.gz: 3ab12e91adb910040a4007140054cc048a0f83e34e54ee649e52ea673a27b95cfb267b56fae98a7605616e4c3f55d17af403c99bf9ba2a5d504dffdd917a7cbb
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.1.0](https://github.com/bensheldon/good_job/tree/v3.1.0) (2022-07-11)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.0.2...v3.1.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Improve Dashboard display of parameters \(CronEntry kwargs; Process configuration; Job and Execution database values\) [\#662](https://github.com/bensheldon/good_job/pull/662) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Don't delegate `GoodJob::Job#status` to executions to avoid race condition [\#661](https://github.com/bensheldon/good_job/pull/661) ([bensheldon](https://github.com/bensheldon))
14
+
15
+ **Closed issues:**
16
+
17
+ - How to suppress repetitive logs in development? [\#658](https://github.com/bensheldon/good_job/issues/658)
18
+ - 500 Internal Server Error Exception in web interface trying to view running jobs [\#656](https://github.com/bensheldon/good_job/issues/656)
19
+ - Cron schedule page in dashboard not showing kwargs [\#608](https://github.com/bensheldon/good_job/issues/608)
20
+ - Paralelism x database connections [\#569](https://github.com/bensheldon/good_job/issues/569)
21
+
22
+ **Merged pull requests:**
23
+
24
+ - Show job/cron/process counts in the Navbar [\#663](https://github.com/bensheldon/good_job/pull/663) ([bensheldon](https://github.com/bensheldon))
25
+ - Dequeing should be first-in first-out [\#651](https://github.com/bensheldon/good_job/pull/651) ([jrochkind](https://github.com/jrochkind))
26
+
3
27
  ## [v3.0.2](https://github.com/bensheldon/good_job/tree/v3.0.2) (2022-07-10)
4
28
 
5
29
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.0.1...v3.0.2)
@@ -56,7 +56,7 @@ module GoodJob
56
56
  end
57
57
 
58
58
  def show
59
- @job = Job.find(params[:id])
59
+ @job = Job.includes_advisory_locks.find(params[:id])
60
60
  end
61
61
 
62
62
  def discard
@@ -38,7 +38,7 @@
38
38
  </div>
39
39
  <% end %>
40
40
  <div>
41
- <%= tag.pre JSON.pretty_generate(execution.serialized_params), id: dom_id(execution, "params"), class: "collapse bg-light card card-body p-3 my-3" %>
41
+ <%= tag.pre JSON.pretty_generate(execution.display_serialized_params), id: dom_id(execution, "params"), class: "collapse bg-light card card-body p-3 my-3" %>
42
42
  </div>
43
43
  <% end %>
44
44
  <% end %>
@@ -13,7 +13,7 @@
13
13
  <th>Executions</th>
14
14
  <th>Error</th>
15
15
  <th>
16
- ActiveJob Params&nbsp;
16
+ Parameters&nbsp;
17
17
  <%= tag.button "Toggle", type: "button", class: "btn btn-sm btn-outline-primary", role: "button",
18
18
  data: { bs_toggle: "collapse", bs_target: ".job-params" },
19
19
  aria: { expanded: false, controls: jobs.map { |job| "##{dom_id(job, "params")}" }.join(" ") }
@@ -71,7 +71,7 @@
71
71
  data: { bs_toggle: "collapse", bs_target: "##{dom_id(job, 'params')}" },
72
72
  aria: { expanded: false, controls: dom_id(job, "params") }
73
73
  %>
74
- <%= tag.pre JSON.pretty_generate(job.serialized_params), id: dom_id(job, "params"), class: "collapse job-params" %>
74
+ <%= tag.pre JSON.pretty_generate(job.display_serialized_params), id: dom_id(job, "params"), class: "collapse job-params" %>
75
75
  </td>
76
76
  <td>
77
77
  <div class="text-nowrap">
@@ -54,4 +54,4 @@
54
54
  <%= tag.pre @job.serialized_params["arguments"].map(&:inspect).join(', ') %>
55
55
  </div>
56
56
 
57
- <%= render 'executions', executions: @job.executions.reverse %>
57
+ <%= render 'executions', executions: @job.executions.includes_advisory_locks.reverse %>
@@ -11,7 +11,7 @@
11
11
  <li class="nav-item">
12
12
  <%= link_to url_for({state: name}), class: "nav-link #{"active" if params[:state] == name}" do %>
13
13
  <%= t(name, scope: 'good_job.status') %>
14
- <span class="badge bg-primary rounded-pill <%= "bg-secondary" if count == 0 %>"><%= count %></span>
14
+ <span class="badge bg-primary rounded-pill <%= "bg-secondary" if count == 0 %>"><%= number_with_delimiter(count) %></span>
15
15
  <% end %>
16
16
  </li>
17
17
  <% end %>
@@ -8,13 +8,25 @@
8
8
  <div class="collapse navbar-collapse" id="navbarSupportedContent">
9
9
  <ul class="navbar-nav me-auto">
10
10
  <li class="nav-item">
11
- <%= link_to t(".jobs"), jobs_path, class: ["nav-link", ("active" if controller_name == 'jobs')] %>
11
+ <%= link_to jobs_path, class: ["nav-link", ("active" if controller_name == 'jobs')] do %>
12
+ <%= t(".jobs") %>
13
+ <% jobs_count = GoodJob::Job.count %>
14
+ <span class="badge bg-secondary rounded-pill"><%= number_to_human(jobs_count) %></span>
15
+ <% end %>
12
16
  </li>
13
17
  <li class="nav-item">
14
- <%= link_to t(".cron_schedules"), cron_entries_path, class: ["nav-link", ("active" if controller_name == 'cron_entries')] %>
18
+ <%= link_to cron_entries_path, class: ["nav-link", ("active" if controller_name == 'cron_entries')] do %>
19
+ <%= t(".cron_schedules") %>
20
+ <% cron_entries_count = GoodJob::CronEntry.all.size %>
21
+ <span class="badge bg-secondary rounded-pill"><%= cron_entries_count %></span>
22
+ <% end %>
15
23
  </li>
16
24
  <li class="nav-item">
17
- <%= link_to t(".processes"), processes_path, class: ["nav-link", ("active" if controller_name == 'processes')] %>
25
+ <%= link_to processes_path, class: ["nav-link", ("active" if controller_name == 'processes')] do %>
26
+ <%= t(".processes") %>
27
+ <% processes_count = GoodJob::Process.count %>
28
+ <span class="badge bg-secondary rounded-pill <%= "bg-danger" if processes_count == 0 %>"><%= processes_count %></span>
29
+ <% end %>
18
30
  </li>
19
31
  </ul>
20
32
  <div class="nav-item pe-2">
@@ -63,3 +63,22 @@ en:
63
63
  retried: Retried
64
64
  running: Running
65
65
  scheduled: Scheduled
66
+ number:
67
+ format:
68
+ delimiter: ","
69
+ separator: "."
70
+ human:
71
+ decimal_units:
72
+ format: "%n%u"
73
+ units:
74
+ billion: B
75
+ hundred: ''
76
+ million: M
77
+ quadrillion: Q
78
+ thousand: K
79
+ trillion: T
80
+ unit: ''
81
+ format:
82
+ delimiter: ","
83
+ precision: 3
84
+ separator: "."
@@ -63,3 +63,22 @@ es:
63
63
  retried: reintentado
64
64
  running: Corriendo
65
65
  scheduled: Programado
66
+ number:
67
+ format:
68
+ delimiter: " "
69
+ separator: ","
70
+ human:
71
+ decimal_units:
72
+ format: "%n%u"
73
+ units:
74
+ billion: B
75
+ hundred: ''
76
+ million: M
77
+ quadrillion: q
78
+ thousand: k
79
+ trillion: T
80
+ unit: ''
81
+ format:
82
+ delimiter: " "
83
+ precision: 3
84
+ separator: ","
@@ -63,3 +63,22 @@ nl:
63
63
  retried: Opnieuw geprobeerd
64
64
  running: Rennen
65
65
  scheduled: Gepland
66
+ number:
67
+ format:
68
+ delimiter: "."
69
+ separator: ","
70
+ human:
71
+ decimal_units:
72
+ format: "%n%u"
73
+ units:
74
+ billion: B
75
+ hundred: ''
76
+ million: M
77
+ quadrillion: Q
78
+ thousand: K
79
+ trillion: T
80
+ unit: ''
81
+ format:
82
+ delimiter: "."
83
+ precision: 3
84
+ separator: ","
@@ -87,3 +87,22 @@ ru:
87
87
  retried: Повторная попытка
88
88
  running: Бег
89
89
  scheduled: по расписанию
90
+ number:
91
+ format:
92
+ delimiter: " "
93
+ separator: ","
94
+ human:
95
+ decimal_units:
96
+ format: "%n%u"
97
+ units:
98
+ billion: Б
99
+ hundred: ''
100
+ million: М
101
+ quadrillion: Q
102
+ thousand: К
103
+ trillion: Т
104
+ unit: ''
105
+ format:
106
+ delimiter: " "
107
+ precision: 3
108
+ separator: ","
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '3.0.2'
4
+ VERSION = '3.1.0'
5
5
  end
@@ -112,9 +112,11 @@ module GoodJob # :nodoc:
112
112
  class: job_class,
113
113
  cron: schedule,
114
114
  set: display_property(set),
115
- args: display_property(args),
116
115
  description: display_property(description),
117
- }
116
+ }.tap do |properties|
117
+ properties[:args] = display_property(args) if args.present?
118
+ properties[:kwargs] = display_property(kwargs) if kwargs.present?
119
+ end
118
120
  end
119
121
 
120
122
  private
@@ -88,6 +88,18 @@ module GoodJob
88
88
  # @return [ActiveRecord::Relation]
89
89
  scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
90
90
 
91
+ # Order jobs by created_at, for first-in first-out
92
+ # @!method creation_ordered
93
+ # @!scope class
94
+ # @return [ActiveRecord:Relation]
95
+ scope :creation_ordered, -> { order('created_at ASC') }
96
+
97
+ # Order jobs for de-queueing
98
+ # @!method dequeue_ordered
99
+ # @!scope class
100
+ # @return [ActiveRecord:Relation]
101
+ scope :dequeue_ordered, -> { priority_ordered.creation_ordered }
102
+
91
103
  # Order jobs by scheduled or created (oldest first).
92
104
  # @!method schedule_ordered
93
105
  # @!scope class
@@ -154,7 +166,7 @@ module GoodJob
154
166
  # raised, if any (if the job raised, then the second array entry will be
155
167
  # +nil+). If there were no jobs to execute, returns +nil+.
156
168
  def self.perform_with_advisory_lock
157
- unfinished.priority_ordered.only_scheduled.limit(1).with_advisory_lock(unlock_session: true) do |executions|
169
+ unfinished.dequeue_ordered.only_scheduled.limit(1).with_advisory_lock(unlock_session: true) do |executions|
158
170
  execution = executions.first
159
171
  break if execution.blank?
160
172
  break :unlocked unless execution&.executable?
@@ -279,7 +291,9 @@ module GoodJob
279
291
  # @return [Symbol]
280
292
  def status
281
293
  if finished_at.present?
282
- if error.present?
294
+ if error.present? && retried_good_job_id.present?
295
+ :retried
296
+ elsif error.present? && retried_good_job_id.nil?
283
297
  :discarded
284
298
  else
285
299
  :finished
@@ -297,8 +311,20 @@ module GoodJob
297
311
  end
298
312
  end
299
313
 
314
+ # Return formatted serialized_params for display in the dashboard
315
+ # @return [Hash]
316
+ def display_serialized_params
317
+ serialized_params.merge({
318
+ _good_job: attributes.except('serialized_params', 'locktype', 'owns_advisory_lock'),
319
+ })
320
+ end
321
+
300
322
  def running?
301
- performed_at? && !finished_at?
323
+ if has_attribute?(:locktype)
324
+ self['locktype'].present?
325
+ else
326
+ advisory_locked?
327
+ end
302
328
  end
303
329
 
304
330
  def number
@@ -314,7 +340,7 @@ module GoodJob
314
340
  def queue_latency
315
341
  now = Time.zone.now
316
342
  expected_start = scheduled_at || created_at
317
- actual_start = performed_at || now
343
+ actual_start = performed_at || finished_at || now
318
344
 
319
345
  actual_start - expected_start unless expected_start >= now
320
346
  end
@@ -3,7 +3,8 @@ module GoodJob
3
3
  # ActiveRecord model that represents an +ActiveJob+ job.
4
4
  # There is not a table in the database whose discrete rows represents "Jobs".
5
5
  # The +good_jobs+ table is a table of individual {GoodJob::Execution}s that share the same +active_job_id+.
6
- # A single row from the +good_jobs+ table of executions is fetched to represent an Job
6
+ # A single row from the +good_jobs+ table of executions is fetched to represent a Job.
7
+ #
7
8
  class Job < BaseRecord
8
9
  include Filterable
9
10
  include Lockable
@@ -72,9 +73,51 @@ module GoodJob
72
73
  serialized_params['job_class']
73
74
  end
74
75
 
75
- # The status of the Job, based on the state of its most recent execution.
76
- # @return [Symbol]
77
- delegate :status, :last_status_at, to: :head_execution
76
+ def last_status_at
77
+ finished_at || performed_at || scheduled_at || created_at
78
+ end
79
+
80
+ def status
81
+ if finished_at.present?
82
+ if error.present? && retried_good_job_id.present?
83
+ :retried
84
+ elsif error.present? && retried_good_job_id.nil?
85
+ :discarded
86
+ else
87
+ :finished
88
+ end
89
+ elsif (scheduled_at || created_at) > DateTime.current
90
+ if serialized_params.fetch('executions', 0) > 1
91
+ :retried
92
+ else
93
+ :scheduled
94
+ end
95
+ elsif running?
96
+ :running
97
+ else
98
+ :queued
99
+ end
100
+ end
101
+
102
+ # Override #reload to add a custom scope to ensure the reloaded record is the head execution
103
+ # @return [Job]
104
+ def reload(options = nil)
105
+ self.class.connection.clear_query_cache
106
+
107
+ # override with the `where(retried_good_job_id: nil)` scope
108
+ override_query = self.class.where(retried_good_job_id: nil)
109
+ fresh_object =
110
+ if options && options[:lock]
111
+ self.class.unscoped { override_query.lock(options[:lock]).find(id) }
112
+ else
113
+ self.class.unscoped { override_query.find(id) }
114
+ end
115
+
116
+ @attributes = fresh_object.instance_variable_get(:@attributes)
117
+ @new_record = false
118
+ @previously_new_record = false
119
+ self
120
+ end
78
121
 
79
122
  # This job's most recent {Execution}
80
123
  # @param reload [Booelan] whether to reload executions
@@ -94,7 +137,7 @@ module GoodJob
94
137
  # The number of times this job has been executed, according to ActiveJob's serialized state.
95
138
  # @return [Numeric]
96
139
  def executions_count
97
- aj_count = head_execution.serialized_params.fetch('executions', 0)
140
+ aj_count = serialized_params.fetch('executions', 0)
98
141
  # The execution count within serialized_params is not updated
99
142
  # once the underlying execution has been executed.
100
143
  if status.in? [:discarded, :finished, :running]
@@ -114,7 +157,15 @@ module GoodJob
114
157
  # If the job has been retried, the error will be fetched from the previous {Execution} record.
115
158
  # @return [String]
116
159
  def recent_error
117
- head_execution.error || executions[-2]&.error
160
+ error || executions[-2]&.error
161
+ end
162
+
163
+ # Return formatted serialized_params for display in the dashboard
164
+ # @return [Hash]
165
+ def display_serialized_params
166
+ serialized_params.merge({
167
+ _good_job: attributes.except('serialized_params', 'locktype', 'owns_advisory_lock'),
168
+ })
118
169
  end
119
170
 
120
171
  # Tests whether the job is being executed right now.
@@ -192,7 +243,6 @@ module GoodJob
192
243
 
193
244
  raise ActionForStateMismatchError if execution.finished_at.present?
194
245
 
195
- execution = head_execution(reload: true)
196
246
  execution.update(scheduled_at: scheduled_at)
197
247
  end
198
248
  end
@@ -45,6 +45,8 @@ module GoodJob # :nodoc:
45
45
  hostname: Socket.gethostname,
46
46
  pid: ::Process.pid,
47
47
  proctitle: $PROGRAM_NAME,
48
+ preserve_job_records: GoodJob.preserve_job_records,
49
+ retry_on_unhandled_error: GoodJob.retry_on_unhandled_error,
48
50
  schedulers: GoodJob::Scheduler.instances.map(&:name),
49
51
  }
50
52
  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.0.2
4
+ version: 3.1.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: 2022-07-10 00:00:00.000000000 Z
11
+ date: 2022-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob