good_job 3.15.3 → 3.15.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/app/models/good_job/base_execution.rb +14 -1
- data/app/models/good_job/discrete_execution.rb +52 -0
- data/app/models/good_job/execution.rb +118 -15
- data/app/models/good_job/job.rb +10 -2
- data/app/views/good_job/jobs/show.html.erb +30 -4
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +18 -0
- data/lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb +32 -0
- data/lib/good_job/adapter.rb +9 -5
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +15 -5
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee08a99ae7d26df57e3f0a7a64b737e987041815805117ac6d4e6119df229932
|
4
|
+
data.tar.gz: fd747fed38d2227bde7898438b6d597d55c5cb72f6f7125171b5637aff168d5a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e32e66f000d6d7cd0a84fd6ab5a7bdf08f2a5f996bd5533ffc11f68ba33ee96e9d4ddc90a64dea1dbfcad43b07b75fcb34ed66c38546cda52b2cd5c0da90a329
|
7
|
+
data.tar.gz: 8f2a8c72500ee21f634ae6436d12ddc0641e730281b8b9534189f5892b795c6dc3a214e30e4a348878285a8912342dea3fbc74a6ab615a4de5b38f025767db3f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.15.4](https://github.com/bensheldon/good_job/tree/v3.15.4) (2023-04-22)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.15.3...v3.15.4)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Create "discrete" `good_job_executions` table to separate Job records from Execution records and have a 1-to-1 correspondence between `good_jobs` records and Active Job jobs [\#928](https://github.com/bensheldon/good_job/pull/928) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
|
3
11
|
## [v3.15.3](https://github.com/bensheldon/good_job/tree/v3.15.3) (2023-04-22)
|
4
12
|
|
5
13
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.15.2...v3.15.3)
|
@@ -33,12 +33,25 @@ module GoodJob
|
|
33
33
|
def coalesce_scheduled_at_created_at
|
34
34
|
arel_table.coalesce(arel_table['scheduled_at'], arel_table['created_at'])
|
35
35
|
end
|
36
|
+
|
37
|
+
def discrete_support?
|
38
|
+
if connection.table_exists?('good_job_executions')
|
39
|
+
true
|
40
|
+
else
|
41
|
+
migration_pending_warning!
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
36
45
|
end
|
37
46
|
|
38
47
|
# The ActiveJob job class, as a string
|
39
48
|
# @return [String]
|
40
49
|
def job_class
|
41
|
-
serialized_params['job_class']
|
50
|
+
discrete? ? attributes['job_class'] : serialized_params['job_class']
|
51
|
+
end
|
52
|
+
|
53
|
+
def discrete?
|
54
|
+
self.class.discrete_support? && is_discrete?
|
42
55
|
end
|
43
56
|
end
|
44
57
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GoodJob # :nodoc:
|
4
|
+
class DiscreteExecution < BaseRecord
|
5
|
+
self.table_name = 'good_job_executions'
|
6
|
+
|
7
|
+
belongs_to :execution, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :discrete_executions, optional: true
|
8
|
+
belongs_to :job, class_name: 'GoodJob::Job', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :discrete_executions, optional: true
|
9
|
+
|
10
|
+
scope :finished, -> { where.not(finished_at: nil) }
|
11
|
+
|
12
|
+
alias_attribute :performed_at, :created_at
|
13
|
+
|
14
|
+
def number
|
15
|
+
serialized_params.fetch('executions', 0) + 1
|
16
|
+
end
|
17
|
+
|
18
|
+
# Time between when this job was expected to run and when it started running
|
19
|
+
def queue_latency
|
20
|
+
created_at - scheduled_at
|
21
|
+
end
|
22
|
+
|
23
|
+
# Time between when this job started and finished
|
24
|
+
def runtime_latency
|
25
|
+
(finished_at || Time.current) - performed_at if performed_at
|
26
|
+
end
|
27
|
+
|
28
|
+
def last_status_at
|
29
|
+
finished_at || created_at
|
30
|
+
end
|
31
|
+
|
32
|
+
def status
|
33
|
+
if finished_at.present?
|
34
|
+
if error.present?
|
35
|
+
:retried
|
36
|
+
elsif error.present? && job.finished_at.present?
|
37
|
+
:discarded
|
38
|
+
else
|
39
|
+
:succeeded
|
40
|
+
end
|
41
|
+
else
|
42
|
+
:running
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def display_serialized_params
|
47
|
+
serialized_params.merge({
|
48
|
+
_good_job_execution: attributes.except('serialized_params'),
|
49
|
+
})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -70,9 +70,13 @@ module GoodJob
|
|
70
70
|
end
|
71
71
|
|
72
72
|
belongs_to :batch, class_name: 'GoodJob::BatchRecord', optional: true, inverse_of: :executions
|
73
|
-
|
74
73
|
belongs_to :job, class_name: 'GoodJob::Job', foreign_key: 'active_job_id', primary_key: 'active_job_id', optional: true, inverse_of: :executions
|
75
|
-
|
74
|
+
has_many :discrete_executions, class_name: 'GoodJob::DiscreteExecution', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :execution # rubocop:disable Rails/HasManyOrHasOneDependent
|
75
|
+
|
76
|
+
after_destroy lambda {
|
77
|
+
GoodJob::DiscreteExecution.where(active_job_id: active_job_id).delete_all if discrete? # TODO: move into association `dependent: :delete_all` after v4
|
78
|
+
self.class.active_job_id(active_job_id).delete_all
|
79
|
+
}, if: -> { @_destroy_job }
|
76
80
|
|
77
81
|
# Get executions with given ActiveJob ID
|
78
82
|
# @!method active_job_id
|
@@ -201,8 +205,12 @@ module GoodJob
|
|
201
205
|
end
|
202
206
|
end)
|
203
207
|
|
204
|
-
# Construct a GoodJob::Execution from an ActiveJob instance.
|
205
208
|
def self.build_for_enqueue(active_job, overrides = {})
|
209
|
+
new(**enqueue_args(active_job, overrides))
|
210
|
+
end
|
211
|
+
|
212
|
+
# Construct arguments for GoodJob::Execution from an ActiveJob instance.
|
213
|
+
def self.enqueue_args(active_job, overrides = {})
|
206
214
|
if active_job.priority && GoodJob.configuration.smaller_number_is_higher_priority.nil?
|
207
215
|
ActiveSupport::Deprecation.warn(<<~DEPRECATION)
|
208
216
|
The next major version of GoodJob (v4.0) will change job `priority` to give smaller numbers higher priority (default: `0`), in accordance with Active Job's definition of priority.
|
@@ -218,6 +226,7 @@ module GoodJob
|
|
218
226
|
serialized_params: active_job.serialize,
|
219
227
|
scheduled_at: active_job.scheduled_at,
|
220
228
|
}
|
229
|
+
|
221
230
|
execution_args[:concurrency_key] = active_job.good_job_concurrency_key if active_job.respond_to?(:good_job_concurrency_key)
|
222
231
|
|
223
232
|
reenqueued_current_execution = CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
|
@@ -238,7 +247,7 @@ module GoodJob
|
|
238
247
|
execution_args[:cron_at] = CurrentThread.cron_at
|
239
248
|
end
|
240
249
|
|
241
|
-
|
250
|
+
execution_args.merge(overrides)
|
242
251
|
end
|
243
252
|
|
244
253
|
# Finds the next eligible Execution, acquire an advisory lock related to it, and
|
@@ -298,19 +307,47 @@ module GoodJob
|
|
298
307
|
# The new {Execution} instance representing the queued ActiveJob job.
|
299
308
|
def self.enqueue(active_job, scheduled_at: nil, create_with_advisory_lock: false)
|
300
309
|
ActiveSupport::Notifications.instrument("enqueue_job.good_job", { active_job: active_job, scheduled_at: scheduled_at, create_with_advisory_lock: create_with_advisory_lock }) do |instrument_payload|
|
301
|
-
|
310
|
+
current_execution = CurrentThread.execution
|
311
|
+
|
312
|
+
retried = current_execution && current_execution.active_job_id == active_job.job_id
|
313
|
+
if retried
|
314
|
+
if current_execution.discrete?
|
315
|
+
execution = current_execution
|
316
|
+
execution.assign_attributes(enqueue_args(active_job, { scheduled_at: scheduled_at }))
|
317
|
+
execution.scheduled_at ||= Time.current
|
318
|
+
execution.performed_at = nil
|
319
|
+
execution.finished_at = nil
|
320
|
+
else
|
321
|
+
execution = build_for_enqueue(active_job, { scheduled_at: scheduled_at })
|
322
|
+
end
|
323
|
+
else
|
324
|
+
execution = build_for_enqueue(active_job, { scheduled_at: scheduled_at })
|
325
|
+
execution.make_discrete if discrete_support?
|
326
|
+
end
|
302
327
|
|
303
|
-
|
304
|
-
|
328
|
+
if create_with_advisory_lock
|
329
|
+
if execution.persisted?
|
330
|
+
execution.advisory_lock
|
331
|
+
else
|
332
|
+
execution.create_with_advisory_lock = true
|
333
|
+
end
|
334
|
+
end
|
305
335
|
|
336
|
+
instrument_payload[:execution] = execution
|
306
337
|
execution.save!
|
307
|
-
active_job.provider_job_id = execution.id
|
308
|
-
CurrentThread.execution.retried_good_job_id = execution.id if CurrentThread.active_job_id && CurrentThread.active_job_id == active_job.job_id
|
309
338
|
|
339
|
+
CurrentThread.execution.retried_good_job_id = execution.id if retried && !CurrentThread.execution.discrete?
|
340
|
+
active_job.provider_job_id = execution.id
|
310
341
|
execution
|
311
342
|
end
|
312
343
|
end
|
313
344
|
|
345
|
+
def self.format_error(error)
|
346
|
+
raise ArgumentError unless error.is_a?(Exception)
|
347
|
+
|
348
|
+
[error.class.to_s, ERROR_MESSAGE_SEPARATOR, error.message].join
|
349
|
+
end
|
350
|
+
|
314
351
|
# Execute the ActiveJob job this {Execution} represents.
|
315
352
|
# @return [ExecutionResult]
|
316
353
|
# An array of the return value of the job's +#perform+ method and the
|
@@ -320,12 +357,39 @@ module GoodJob
|
|
320
357
|
run_callbacks(:perform) do
|
321
358
|
raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
|
322
359
|
|
360
|
+
discrete_execution = nil
|
323
361
|
result = GoodJob::CurrentThread.within do |current_thread|
|
324
362
|
current_thread.reset
|
325
363
|
current_thread.execution = self
|
326
364
|
|
327
|
-
|
328
|
-
|
365
|
+
if performed_at
|
366
|
+
current_thread.execution_interrupted = performed_at
|
367
|
+
|
368
|
+
if discrete?
|
369
|
+
interrupt_error_string = self.class.format_error(GoodJob::InterruptError.new("Interrupted after starting perform at '#{performed_at}'"))
|
370
|
+
self.error = interrupt_error_string
|
371
|
+
discrete_executions.where(finished_at: nil).where.not(performed_at: nil).update_all( # rubocop:disable Rails/SkipsModelValidations
|
372
|
+
error: interrupt_error_string,
|
373
|
+
finished_at: Time.current
|
374
|
+
)
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
if discrete?
|
379
|
+
transaction do
|
380
|
+
now = Time.current
|
381
|
+
discrete_execution = discrete_executions.create!(
|
382
|
+
job_class: job_class,
|
383
|
+
queue_name: queue_name,
|
384
|
+
serialized_params: serialized_params,
|
385
|
+
scheduled_at: (scheduled_at || created_at),
|
386
|
+
created_at: now
|
387
|
+
)
|
388
|
+
update!(performed_at: now, executions_count: ((executions_count || 0) + 1))
|
389
|
+
end
|
390
|
+
else
|
391
|
+
update!(performed_at: Time.current)
|
392
|
+
end
|
329
393
|
|
330
394
|
ActiveSupport::Notifications.instrument("perform_job.good_job", { execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do |instrument_payload|
|
331
395
|
value = ActiveJob::Base.execute(active_job_data)
|
@@ -349,14 +413,42 @@ module GoodJob
|
|
349
413
|
end
|
350
414
|
|
351
415
|
job_error = result.handled_error || result.unhandled_error
|
352
|
-
|
416
|
+
|
417
|
+
if job_error
|
418
|
+
error_string = self.class.format_error(job_error)
|
419
|
+
self.error = error_string
|
420
|
+
discrete_execution.error = error_string if discrete_execution
|
421
|
+
else
|
422
|
+
self.error = nil
|
423
|
+
end
|
353
424
|
|
354
425
|
reenqueued = result.retried? || retried_good_job_id.present?
|
355
426
|
if result.unhandled_error && GoodJob.retry_on_unhandled_error
|
356
|
-
|
427
|
+
if discrete_execution
|
428
|
+
transaction do
|
429
|
+
discrete_execution.update!(finished_at: Time.current)
|
430
|
+
update!(performed_at: nil, finished_at: nil, retried_good_job_id: nil)
|
431
|
+
end
|
432
|
+
else
|
433
|
+
save!
|
434
|
+
end
|
357
435
|
elsif GoodJob.preserve_job_records == true || reenqueued || (result.unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error) || cron_key.present?
|
358
|
-
|
359
|
-
|
436
|
+
now = Time.current
|
437
|
+
if discrete_execution
|
438
|
+
if reenqueued
|
439
|
+
self.performed_at = nil
|
440
|
+
else
|
441
|
+
self.finished_at = now
|
442
|
+
end
|
443
|
+
discrete_execution.finished_at = now
|
444
|
+
transaction do
|
445
|
+
discrete_execution.save!
|
446
|
+
save!
|
447
|
+
end
|
448
|
+
else
|
449
|
+
self.finished_at = now
|
450
|
+
save!
|
451
|
+
end
|
360
452
|
else
|
361
453
|
destroy_job
|
362
454
|
end
|
@@ -371,6 +463,17 @@ module GoodJob
|
|
371
463
|
self.class.unscoped.unfinished.owns_advisory_locked.exists?(id: id)
|
372
464
|
end
|
373
465
|
|
466
|
+
def make_discrete
|
467
|
+
self.is_discrete = true
|
468
|
+
self.id = active_job_id
|
469
|
+
self.job_class = serialized_params['job_class']
|
470
|
+
self.executions_count ||= 0
|
471
|
+
|
472
|
+
current_time = Time.current
|
473
|
+
self.created_at ||= current_time
|
474
|
+
self.scheduled_at ||= current_time
|
475
|
+
end
|
476
|
+
|
374
477
|
# Build an ActiveJob instance and deserialize the arguments, using `#active_job_data`.
|
375
478
|
#
|
376
479
|
# @param ignore_deserialization_errors [Boolean]
|
data/app/models/good_job/job.rb
CHANGED
@@ -30,6 +30,11 @@ module GoodJob
|
|
30
30
|
|
31
31
|
belongs_to :batch, class_name: 'GoodJob::BatchRecord', inverse_of: :jobs, optional: true
|
32
32
|
has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', inverse_of: :job # rubocop:disable Rails/HasManyOrHasOneDependent
|
33
|
+
has_many :discrete_executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::DiscreteExecution', foreign_key: 'active_job_id', primary_key: :active_job_id, inverse_of: :job # rubocop:disable Rails/HasManyOrHasOneDependent
|
34
|
+
|
35
|
+
after_destroy lambda {
|
36
|
+
GoodJob::DiscreteExecution.where(active_job_id: active_job_id).delete_all if discrete? # TODO: move into association `dependent: :delete_all` after v4
|
37
|
+
}
|
33
38
|
|
34
39
|
# Only the most-recent unretried execution represents a "Job"
|
35
40
|
default_scope { where(retried_good_job_id: nil) }
|
@@ -56,6 +61,8 @@ module GoodJob
|
|
56
61
|
# Errored but will not be retried
|
57
62
|
scope :discarded, -> { finished.where.not(error: nil) }
|
58
63
|
|
64
|
+
scope :unfinished_undiscrete, -> { where(finished_at: nil, retried_good_job_id: nil, is_discrete: [nil, false]) }
|
65
|
+
|
59
66
|
# The job's ActiveJob UUID
|
60
67
|
# @return [String]
|
61
68
|
def id
|
@@ -191,9 +198,10 @@ module GoodJob
|
|
191
198
|
|
192
199
|
execution.class.transaction(joinable: false, requires_new: true) do
|
193
200
|
new_active_job = active_job.retry_job(wait: 0, error: execution.error)
|
194
|
-
execution.save
|
201
|
+
execution.save!
|
195
202
|
end
|
196
203
|
end
|
204
|
+
|
197
205
|
new_active_job
|
198
206
|
end
|
199
207
|
end
|
@@ -213,7 +221,7 @@ module GoodJob
|
|
213
221
|
update_execution = proc do
|
214
222
|
execution.update(
|
215
223
|
finished_at: Time.current,
|
216
|
-
error:
|
224
|
+
error: GoodJob::Execution.format_error(job_error)
|
217
225
|
)
|
218
226
|
end
|
219
227
|
|
@@ -3,7 +3,12 @@
|
|
3
3
|
<nav aria-label="breadcrumb">
|
4
4
|
<ol class="breadcrumb small mb-0">
|
5
5
|
<li class="breadcrumb-item"><%= link_to t(".jobs"), jobs_path %></li>
|
6
|
-
<li class="breadcrumb-item active" aria-current="page"
|
6
|
+
<li class="breadcrumb-item active" aria-current="page">
|
7
|
+
<%= tag.code @job.id, class: "text-muted" %>
|
8
|
+
<% if @job.discrete? %>
|
9
|
+
<span class="badge bg-info text-dark">Discrete</span>
|
10
|
+
<% end %>
|
11
|
+
</li>
|
7
12
|
</ol>
|
8
13
|
</nav>
|
9
14
|
<div class="row align-items-center">
|
@@ -21,6 +26,10 @@
|
|
21
26
|
<div class="font-monospace fw-bold small my-2"><%= tag.strong @job.priority %></div>
|
22
27
|
</div>
|
23
28
|
<div class="col text-end">
|
29
|
+
<div class="mb-2">
|
30
|
+
<%= tag.span relative_time(@job.last_status_at), class: "small" %>
|
31
|
+
<%= status_badge @job.status %>
|
32
|
+
</div>
|
24
33
|
<% if @job.status.in? [:scheduled, :retried, :queued] %>
|
25
34
|
<%= button_to reschedule_job_path(@job.id), method: :put,
|
26
35
|
class: "btn btn-sm btn-outline-primary",
|
@@ -59,8 +68,25 @@
|
|
59
68
|
</div>
|
60
69
|
|
61
70
|
<div class="my-4">
|
62
|
-
<h5
|
63
|
-
|
71
|
+
<h5>
|
72
|
+
<%= t "good_job.models.job.arguments" %>
|
73
|
+
<%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
|
74
|
+
title: t("good_job.actions.inspect"),
|
75
|
+
data: { bs_toggle: "collapse", bs_target: "##{dom_id(@job, 'params')}" },
|
76
|
+
aria: { expanded: false, controls: dom_id(@job, "params") } do %>
|
77
|
+
<%= render_icon "info" %>
|
78
|
+
<span class="visually-hidden"><%= t "good_job.actions.inspect" %></span>
|
79
|
+
<% end %>
|
80
|
+
</h5>
|
64
81
|
</div>
|
82
|
+
<%= tag.pre @job.serialized_params["arguments"].map(&:inspect).join(', '), class: 'text-wrap text-break' %>
|
83
|
+
|
84
|
+
<%= tag.div id: dom_id(@job, "params"), class: "list-group-item collapse small bg-dark text-light" do %>
|
85
|
+
<%= tag.pre JSON.pretty_generate(@job.display_serialized_params) %>
|
86
|
+
<% end %>
|
65
87
|
|
66
|
-
|
88
|
+
<% if @job.discrete? %>
|
89
|
+
<%= render 'executions', executions: @job.discrete_executions.reverse %>
|
90
|
+
<% else %>
|
91
|
+
<%= render 'executions', executions: @job.executions.includes_advisory_locks.reverse %>
|
92
|
+
<% end %>
|
@@ -22,6 +22,10 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
22
22
|
|
23
23
|
t.uuid :batch_id
|
24
24
|
t.uuid :batch_callback_id
|
25
|
+
|
26
|
+
t.boolean :is_discrete
|
27
|
+
t.integer :executions_count
|
28
|
+
t.text :job_class
|
25
29
|
end
|
26
30
|
|
27
31
|
create_table :good_job_batches, id: :uuid do |t|
|
@@ -38,6 +42,18 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
38
42
|
t.datetime :finished_at
|
39
43
|
end
|
40
44
|
|
45
|
+
create_table :good_job_executions, id: :uuid do |t|
|
46
|
+
t.timestamps
|
47
|
+
|
48
|
+
t.uuid :active_job_id, null: false
|
49
|
+
t.text :job_class
|
50
|
+
t.text :queue_name
|
51
|
+
t.jsonb :serialized_params
|
52
|
+
t.datetime :scheduled_at
|
53
|
+
t.datetime :finished_at
|
54
|
+
t.text :error
|
55
|
+
end
|
56
|
+
|
41
57
|
create_table :good_job_processes, id: :uuid do |t|
|
42
58
|
t.timestamps
|
43
59
|
t.jsonb :state
|
@@ -62,5 +78,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
62
78
|
where: "finished_at IS NULL", name: :index_good_jobs_jobs_on_priority_created_at_when_unfinished
|
63
79
|
add_index :good_jobs, [:batch_id], where: "batch_id IS NOT NULL"
|
64
80
|
add_index :good_jobs, [:batch_callback_id], where: "batch_callback_id IS NOT NULL"
|
81
|
+
|
82
|
+
add_index :good_job_executions, [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
|
65
83
|
end
|
66
84
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class CreateGoodJobExecutions < 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_executions)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
create_table :good_job_executions, id: :uuid do |t|
|
13
|
+
t.timestamps
|
14
|
+
|
15
|
+
t.uuid :active_job_id, null: false
|
16
|
+
t.text :job_class
|
17
|
+
t.text :queue_name
|
18
|
+
t.jsonb :serialized_params
|
19
|
+
t.datetime :scheduled_at
|
20
|
+
t.datetime :finished_at
|
21
|
+
t.text :error
|
22
|
+
|
23
|
+
t.index [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
|
24
|
+
end
|
25
|
+
|
26
|
+
change_table :good_jobs do |t|
|
27
|
+
t.boolean :is_discrete
|
28
|
+
t.integer :executions_count
|
29
|
+
t.text :job_class
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/good_job/adapter.rb
CHANGED
@@ -50,11 +50,15 @@ module GoodJob
|
|
50
50
|
|
51
51
|
current_time = Time.current
|
52
52
|
executions = active_jobs.map do |active_job|
|
53
|
-
GoodJob::Execution.build_for_enqueue(active_job
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
GoodJob::Execution.build_for_enqueue(active_job).tap do |execution|
|
54
|
+
if GoodJob::Execution.discrete_support?
|
55
|
+
execution.make_discrete
|
56
|
+
execution.scheduled_at = current_time if execution.scheduled_at == execution.created_at
|
57
|
+
end
|
58
|
+
|
59
|
+
execution.created_at = current_time
|
60
|
+
execution.updated_at = current_time
|
61
|
+
end
|
58
62
|
end
|
59
63
|
|
60
64
|
inline_executions = []
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
@@ -170,14 +170,19 @@ module GoodJob
|
|
170
170
|
ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { older_than: older_than, timestamp: timestamp }) do |payload|
|
171
171
|
deleted_executions_count = 0
|
172
172
|
deleted_batches_count = 0
|
173
|
+
deleted_discrete_executions_count = 0
|
173
174
|
|
174
175
|
jobs_query = GoodJob::Job.where('finished_at <= ?', timestamp).order(finished_at: :asc).limit(in_batches_of)
|
175
176
|
jobs_query = jobs_query.succeeded unless include_discarded
|
176
177
|
loop do
|
177
|
-
|
178
|
-
break if
|
178
|
+
active_job_ids = jobs_query.pluck(:active_job_id)
|
179
|
+
break if active_job_ids.empty?
|
179
180
|
|
180
|
-
|
181
|
+
deleted_discrete_executions = GoodJob::DiscreteExecution.where(active_job_id: active_job_ids).delete_all
|
182
|
+
deleted_discrete_executions_count += deleted_discrete_executions
|
183
|
+
|
184
|
+
deleted_executions = GoodJob::Execution.where(active_job_id: active_job_ids).delete_all
|
185
|
+
deleted_executions_count += deleted_executions
|
181
186
|
end
|
182
187
|
|
183
188
|
if GoodJob::BatchRecord.migrated?
|
@@ -191,9 +196,14 @@ module GoodJob
|
|
191
196
|
end
|
192
197
|
end
|
193
198
|
|
194
|
-
payload[:destroyed_executions_count] = deleted_executions_count
|
195
199
|
payload[:destroyed_batches_count] = deleted_batches_count
|
196
|
-
payload[:
|
200
|
+
payload[:destroyed_discrete_executions_count] = deleted_discrete_executions_count
|
201
|
+
payload[:destroyed_executions_count] = deleted_executions_count
|
202
|
+
|
203
|
+
destroyed_records_count = deleted_batches_count + deleted_discrete_executions_count + deleted_executions_count
|
204
|
+
payload[:destroyed_records_count] = destroyed_records_count
|
205
|
+
|
206
|
+
destroyed_records_count
|
197
207
|
end
|
198
208
|
end
|
199
209
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.15.
|
4
|
+
version: 3.15.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
@@ -350,6 +350,7 @@ files:
|
|
350
350
|
- app/models/good_job/batch.rb
|
351
351
|
- app/models/good_job/batch_record.rb
|
352
352
|
- app/models/good_job/cron_entry.rb
|
353
|
+
- app/models/good_job/discrete_execution.rb
|
353
354
|
- app/models/good_job/execution.rb
|
354
355
|
- app/models/good_job/execution_result.rb
|
355
356
|
- app/models/good_job/job.rb
|
@@ -401,6 +402,7 @@ files:
|
|
401
402
|
- lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb
|
402
403
|
- lib/generators/good_job/templates/update/migrations/03_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb.erb
|
403
404
|
- lib/generators/good_job/templates/update/migrations/04_create_good_job_batches.rb.erb
|
405
|
+
- lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb
|
404
406
|
- lib/generators/good_job/update_generator.rb
|
405
407
|
- lib/good_job.rb
|
406
408
|
- lib/good_job/active_job_extensions/batches.rb
|