good_job 3.11.0 → 3.12.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: '0298665dd1e0d426def8cd08d422e41bfaf308f26de80e7079f80f4ae3403259'
4
- data.tar.gz: 6ed8d5a5891515f4ef6404c1776a9147df1625d5be1a654b0407eb94cc96f36e
3
+ metadata.gz: eeb15f65ff801785a0df591dec95a0e044a4f5d7e61e6b2483003c4b2afd8cb3
4
+ data.tar.gz: e5c631765c1303d1fee0597313bbcb5d3844b85ad49600f2609c83149885872d
5
5
  SHA512:
6
- metadata.gz: c0d4b598ee46722d8a23c6e1d044ba2a6ab9933998b096a1749896bc4fba7b50f19ec71ce02fc5924732210a0ea0a442a79ff66e34c12b94c3b7e79048f1cae9
7
- data.tar.gz: 39fee537236f2304305dc9b5ae794a6045d5fb66450f67e40a859b4f62c05c88dcea2cb85b328839fff94989f260a40a7bc4a76fffffd1107a861bbe7ac19a2b
6
+ metadata.gz: bf0ed8436074ca82d718b64a76b4b1ce36f4bc2a085fe765b04eb101ae7886d0823bb50faa7816fd3a0dcb56d308dc568a7cdd86373d0afcb49bc2d22c045105
7
+ data.tar.gz: 8bd8e67ebfcb2b90a7e1ce6a542cf659e8d4cd37f31782a3a3cd5d26cfedba1e85e9a57835a8a9e78cd731f304b055c21802ca1c68fc2fcffb32ec9b3050ebdf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.12.0](https://github.com/bensheldon/good_job/tree/v3.12.0) (2023-02-07)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.11.1...v3.12.0)
6
+
7
+ **Merged pull requests:**
8
+
9
+ - Create `InterruptErrors` extension to raise an exception when an interrupted job is retried [\#830](https://github.com/bensheldon/good_job/pull/830) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ ## [v3.11.1](https://github.com/bensheldon/good_job/tree/v3.11.1) (2023-02-06)
12
+
13
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.11.0...v3.11.1)
14
+
15
+ **Merged pull requests:**
16
+
17
+ - UI improvement [\#829](https://github.com/bensheldon/good_job/pull/829) ([Ajmal](https://github.com/Ajmal))
18
+
3
19
  ## [v3.11.0](https://github.com/bensheldon/good_job/tree/v3.11.0) (2023-02-06)
4
20
 
5
21
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.10.1...v3.11.0)
data/README.md CHANGED
@@ -55,6 +55,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
55
55
  - [Exceptions](#exceptions)
56
56
  - [Retries](#retries)
57
57
  - [ActionMailer retries](#actionmailer-retries)
58
+ - [Interrupts](#interrupts)
58
59
  - [Timeouts](#timeouts)
59
60
  - [Optimize queues, threads, and processes](#optimize-queues-threads-and-processes)
60
61
  - [Database connections](#database-connections)
@@ -829,6 +830,23 @@ end
829
830
  Note, that `ActionMailer::MailDeliveryJob` is a default since Rails 6.0. Be sure that your app is using that class, as it
830
831
  might also be configured to use (deprecated now) `ActionMailer::DeliveryJob`.
831
832
 
833
+ ### Interrupts
834
+
835
+ Jobs will be automatically retried if the process is interrupted while performing a job, for example as the result of a `SIGKILL` or power failure.
836
+
837
+ If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job closs. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use ActiveJob's `retry_on` and `discard_on` to control the behavior of the job.
838
+
839
+ ```ruby
840
+ class MyJob < ApplicationJob
841
+ # The extension must be included before other extensions
842
+ include GoodJob::ActiveJobExtensions::InterruptErrors
843
+ # Discard the job if it is interrupted
844
+ discard_on InterruptError
845
+ # Retry the job if it is interrupted
846
+ retry_on InterruptError, wait: 0, attempts: Float::INFINITY
847
+ end
848
+ ```
849
+
832
850
  ### Timeouts
833
851
 
834
852
  Job timeouts can be configured with an `around_perform`:
@@ -315,10 +315,27 @@ module GoodJob
315
315
  run_callbacks(:perform) do
316
316
  raise PreviouslyPerformedError, 'Cannot perform a job that has already been performed' if finished_at
317
317
 
318
- self.performed_at = Time.current
319
- save! if GoodJob.preserve_job_records
318
+ result = GoodJob::CurrentThread.within do |current_thread|
319
+ current_thread.reset
320
+ current_thread.execution = self
320
321
 
321
- result = execute
322
+ current_thread.execution_interrupted = performed_at if performed_at
323
+ update!(performed_at: Time.current)
324
+
325
+ ActiveSupport::Notifications.instrument("perform_job.good_job", { execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do
326
+ value = ActiveJob::Base.execute(active_job_data)
327
+
328
+ if value.is_a?(Exception)
329
+ handled_error = value
330
+ value = nil
331
+ end
332
+ handled_error ||= current_thread.error_on_retry || current_thread.error_on_discard
333
+
334
+ ExecutionResult.new(value: value, handled_error: handled_error, retried: current_thread.error_on_retry.present?)
335
+ rescue StandardError => e
336
+ ExecutionResult.new(value: nil, unhandled_error: e)
337
+ end
338
+ end
322
339
 
323
340
  job_error = result.handled_error || result.unhandled_error
324
341
  self.error = [job_error.class, ERROR_MESSAGE_SEPARATOR, job_error.message].join if job_error
@@ -408,28 +425,6 @@ module GoodJob
408
425
  end
409
426
  end
410
427
 
411
- # @return [ExecutionResult]
412
- def execute
413
- GoodJob::CurrentThread.within do |current_thread|
414
- current_thread.reset
415
- current_thread.execution = self
416
-
417
- ActiveSupport::Notifications.instrument("perform_job.good_job", { execution: self, process_id: current_thread.process_id, thread_name: current_thread.thread_name }) do
418
- value = ActiveJob::Base.execute(active_job_data)
419
-
420
- if value.is_a?(Exception)
421
- handled_error = value
422
- value = nil
423
- end
424
- handled_error ||= current_thread.error_on_retry || current_thread.error_on_discard
425
-
426
- ExecutionResult.new(value: value, handled_error: handled_error, retried: current_thread.error_on_retry.present?)
427
- rescue StandardError => e
428
- ExecutionResult.new(value: nil, unhandled_error: e)
429
- end
430
- end
431
- end
432
-
433
428
  def reset_batch_values(&block)
434
429
  GoodJob::Batch.within_thread(batch_id: nil, batch_callback_id: nil, &block)
435
430
  end
@@ -3,9 +3,9 @@
3
3
  <header class="list-group-item bg-light">
4
4
  <div class="row small text-muted text-uppercase align-items-center">
5
5
  <div class="col-4">Jobs</div>
6
- <div class="col-1">Queue</div>
7
- <div class="col-1">Priority</div>
8
- <div class="col-1 text-end">Attempts</div>
6
+ <div class="d-none d-md-block col-md-1">Queue</div>
7
+ <div class="d-none d-md-block col-md-1">Priority</div>
8
+ <div class="d-none d-md-block col-md-1 text-end">Attempts</div>
9
9
  <div class="col text-end">
10
10
  <%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
11
11
  data: { bs_toggle: "collapse", bs_target: ".job-params" },
@@ -21,17 +21,18 @@
21
21
  <% jobs.each do |job| %>
22
22
  <div role="row" class="list-group-item list-group-item-action py-3">
23
23
  <div class="row align-items-center">
24
- <div class="col-4">
24
+ <div class="col-md-4">
25
25
  <%= tag.code link_to(job.id, job_path(job), class: "small text-muted text-decoration-none") %>
26
26
  <%= tag.h5 tag.code(link_to(job.job_class, job_path(job), class: "text-reset text-decoration-none")), class: "text-reset mb-0" %>
27
27
  </div>
28
- <div class="col-1">
28
+ <div class="col-md-1">
29
29
  <span class="badge bg-primary bg-opacity-25 text-dark font-monospace"><%= job.queue_name %></span>
30
30
  </div>
31
- <div class="col-1 small text-center">
31
+ <div class="col-md-1 small text-md-center">
32
32
  <span class="font-monospace fw-bold"><%= job.priority %></span>
33
+ <span class="d-md-none">Priority</span>
33
34
  </div>
34
- <div class="col-1 text-center">
35
+ <div class="col-md-1 text-md-center">
35
36
  <% if job.executions_count > 0 && job.status != :finished %>
36
37
  <%= tag.span job.executions_count, class: "badge rounded-pill bg-danger", data: {
37
38
  bs_toggle: "popover",
@@ -42,6 +43,7 @@
42
43
  <% else %>
43
44
  <span class="badge bg-secondary bg-opacity-50 rounded-pill"><%= job.executions_count %></span>
44
45
  <% end %>
46
+ <span class="d-md-none small">Attemp</span>
45
47
  </div>
46
48
  <div class="col d-flex gap-3 align-items-center justify-content-end">
47
49
  <%= tag.span relative_time(job.last_status_at), class: "small" %>
@@ -3,11 +3,11 @@
3
3
  <header class="list-group-item bg-light">
4
4
  <div class="row small text-muted text-uppercase align-items-center">
5
5
  <div class="col-4">Name</div>
6
- <div class="col-1">Created</div>
7
- <div class="col-1">Enqueued</div>
8
- <div class="col-1">Discarded</div>
9
- <div class="col-1">Finished</div>
10
- <div class="col">Jobs</div>
6
+ <div class="col-md-1 d-none d-md-block">Created</div>
7
+ <div class="col-md-1 d-none d-md-block">Enqueued</div>
8
+ <div class="col-md-1 d-none d-md-block">Discarded</div>
9
+ <div class="col-md-1 d-none d-md-block">Finished</div>
10
+ <div class="col-md-1 d-none d-md-block">Jobs</div>
11
11
  <div class="col text-end">
12
12
  <%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
13
13
  data: { bs_toggle: "collapse", bs_target: ".batch-properties" },
@@ -32,11 +32,32 @@
32
32
  <div class="text-muted"><%= batch.description %></div>
33
33
  <% end %>
34
34
  </div>
35
- <div class="col-1 text-wrap"><%= relative_time(batch.created_at) %></div>
36
- <div class="col-1 text-wrap"><%= relative_time(batch.enqueued_at) if batch.enqueued_at %></div>
37
- <div class="col-1 text-wrap"><%= relative_time(batch.discarded_at) if batch.discarded_at %></div>
38
- <div class="col-1 text-wrap"><%= relative_time(batch.finished_at) if batch.finished_at %></div>
39
- <div class="col"><%= batch.jobs.count %></div>
35
+ <div class="col-md-1 text-wrap">
36
+ <div class="d-md-none small text-muted mt-1">Created at</div>
37
+ <%= relative_time(batch.created_at) %>
38
+ </div>
39
+ <div class="col-md-1 text-wrap">
40
+ <% if batch.enqueued_at %>
41
+ <div class="d-md-none small text-muted mt-1">Enqueued at</div>
42
+ <%= relative_time(batch.enqueued_at) %>
43
+ <% end %>
44
+ </div>
45
+ <div class="col-md-1 text-wrap">
46
+ <% if batch.discarded_at %>
47
+ <div class="d-md-none small text-muted mt-1">Discarded at</div>
48
+ <%= relative_time(batch.discarded_at) %>
49
+ <% end %>
50
+ </div>
51
+ <div class="col-md-1 text-wrap">
52
+ <% if batch.finished_at %>
53
+ <div class="d-md-none small text-muted mt-1">Finished at</div>
54
+ <%= relative_time(batch.finished_at) %>
55
+ <% end %>
56
+ </div>
57
+ <div class="col">
58
+ <div class="d-md-none small text-muted mt-1">Jobs</div>
59
+ <%= batch.jobs.count %>
60
+ </div>
40
61
  <div class="col text-end">
41
62
  <%= tag.button type: "button", class: "btn btn-sm text-muted ms-auto", role: "button",
42
63
  title: "Inspect",
@@ -1,3 +1,7 @@
1
+ <div class="border-bottom">
2
+ <h2 class="pt-3 pb-2">Batches</h2>
3
+ </div>
4
+
1
5
  <% if GoodJob::BatchRecord.migrated? %>
2
6
  <%= render 'good_job/batches/table', batches: @filter.records, filter: @filter %>
3
7
  <% if @filter.records.present? %>
@@ -1,7 +1,7 @@
1
- <div class="break-out bg-light border-bottom py-2 mb-3">
2
- <div class="container-fluid pt-2">
1
+ <div class="border-bottom py-2 mb-3">
2
+ <div class="pt-2">
3
3
  <div class="row align-items-center">
4
- <div class="col-5">
4
+ <div class="col">
5
5
  <nav aria-label="breadcrumb">
6
6
  <ol class="breadcrumb small mb-0">
7
7
  <li class="breadcrumb-item"><%= link_to "Batches", batches_path %></li>
@@ -16,7 +16,9 @@
16
16
 
17
17
  <div class="my-4">
18
18
  <h5>Attributes</h5>
19
- <%= tag.pre JSON.pretty_generate @batch.display_attributes, class: 'text-wrap text-break' %>
19
+ <div class="bg-dark text-light p-3 rounded">
20
+ <%= tag.pre JSON.pretty_generate @batch.display_attributes, class: 'text-wrap text-break' %>
21
+ </div>
20
22
  </div>
21
23
 
22
24
  <div class="my-4">
@@ -1,5 +1,5 @@
1
- <div class="bg-light break-out border-bottom">
2
- <h2 class="container-fluid pt-3 pb-2">Cron Schedules</h2>
1
+ <div class="border-bottom">
2
+ <h2 class="pt-3 pb-2">Cron Schedules</h2>
3
3
  </div>
4
4
 
5
5
  <div class="card my-3">
@@ -4,16 +4,16 @@
4
4
  <% executions.each do |execution| %>
5
5
  <%= tag.div id: dom_id(execution), class: "list-group-item py-3" do %>
6
6
  <div class="row align-items-center text-nowrap">
7
- <div class="col-5 d-flex gap-2">
7
+ <div class="col-md-5 d-flex gap-2">
8
8
  <%= tag.span execution.number, class: "badge bg-secondary bg-opacity-50 rounded-pill" %>
9
9
  <%= tag.code link_to(execution.id, "##{dom_id(execution)}", class: "text-muted text-decoration-none small") %>
10
10
  </div>
11
- <div class="col-2 small">
11
+ <div class="col-md-2 small">
12
12
  <% if execution.queue_latency %>
13
13
  <%= format_duration execution.queue_latency %> <span class="text-muted">in queue</span>
14
14
  <% end %>
15
15
  </div>
16
- <div class="col-2 small">
16
+ <div class="col-md-2 small">
17
17
  <% if execution.runtime_latency %>
18
18
  <%= format_duration execution.runtime_latency %> <span class="text-muted">runtime</span>
19
19
  <% end %>
@@ -22,17 +22,16 @@
22
22
  <div class="d-flex gap-3 align-items-center justify-content-end">
23
23
  <%= tag.span relative_time(execution.last_status_at, include_seconds: true), class: "small" %>
24
24
  <%= status_badge execution.status %>
25
+
26
+ <%= tag.button type: "button", class: "btn btn-sm text-muted ms-auto", role: "button",
27
+ title: "Inspect",
28
+ data: { bs_toggle: "collapse", bs_target: "##{dom_id(execution, 'params')}" },
29
+ aria: { expanded: false, controls: dom_id(execution, "params") } do %>
30
+ <%= render_icon "info" %>
31
+ <span class="visually-hidden">Inspect</span>
32
+ <% end %>
25
33
  </div>
26
34
  </div>
27
- <div class="col-auto">
28
- <%= tag.button type: "button", class: "btn btn-sm text-muted ms-auto", role: "button",
29
- title: "Inspect",
30
- data: { bs_toggle: "collapse", bs_target: "##{dom_id(execution, 'params')}" },
31
- aria: { expanded: false, controls: dom_id(execution, "params") } do %>
32
- <%= render_icon "info" %>
33
- <span class="visually-hidden">Inspect</span>
34
- <% end %>
35
- </div>
36
35
  </div>
37
36
  <% if execution.error %>
38
37
  <div class="mt-3 small">
@@ -7,7 +7,7 @@
7
7
  <%= label_tag('toggle_job_ids', "Toggle all jobs", class: "visually-hidden") %>
8
8
  <%= check_box_tag('toggle_job_ids', "1", false, data: { "checkbox-toggle-all": "job_ids" }) %>
9
9
  </div>
10
- <div class="col-4">
10
+ <div class="col-md-4">
11
11
  <%= form.button type: 'submit', name: 'mass_action', value: 'reschedule', class: 'btn btn-sm btn-outline-secondary', title: "Reschedule all", data: { confirm: "Are you sure you want to reschedule the selected jobs?", disable: true } do %>
12
12
  <span class="me-1"><%= render_icon "skip_forward" %></span> Reschedule
13
13
  <% end %>
@@ -33,9 +33,9 @@
33
33
  </div>
34
34
 
35
35
  </div>
36
- <div class="col-1">Queue</div>
37
- <div class="col-1">Priority</div>
38
- <div class="col-1 text-end">Attempts</div>
36
+ <div class="d-none d-md-block col-md-1">Queue</div>
37
+ <div class="d-none d-md-block col-md-1">Priority</div>
38
+ <div class="d-none d-md-block col-md-1 text-end">Attempts</div>
39
39
  <div class="col text-end">
40
40
  <%= tag.button type: "button", class: "btn btn-sm text-muted", role: "button",
41
41
  data: { bs_toggle: "collapse", bs_target: ".job-params" },
@@ -64,17 +64,18 @@
64
64
  <div class="col-auto">
65
65
  <%= check_box_tag 'job_ids[]', job.id, false, id: dom_id(job, :checkbox), data: { "checkbox-toggle-each": "job_ids" } %>
66
66
  </div>
67
- <div class="col-4">
67
+ <div class="col-md-4">
68
68
  <%= tag.code link_to(job.id, job_path(job), class: "small text-muted text-decoration-none") %>
69
69
  <%= tag.h5 tag.code(link_to(job.job_class, job_path(job), class: "text-reset text-decoration-none")), class: "text-reset mb-0" %>
70
70
  </div>
71
- <div class="col-1">
71
+ <div class="col-md-1">
72
72
  <span class="badge bg-primary bg-opacity-25 text-dark font-monospace"><%= job.queue_name %></span>
73
73
  </div>
74
- <div class="col-1 small text-center">
74
+ <div class="col-md-1 small text-md-center">
75
75
  <span class="font-monospace fw-bold"><%= job.priority %></span>
76
+ <span class="d-md-none">Priority</span>
76
77
  </div>
77
- <div class="col-1 text-center">
78
+ <div class="col-md-1 text-md-center">
78
79
  <% if job.executions_count > 0 && job.status != :succeeded %>
79
80
  <%= tag.span job.executions_count, class: "badge rounded-pill bg-danger", data: {
80
81
  bs_toggle: "popover",
@@ -85,6 +86,7 @@
85
86
  <% else %>
86
87
  <span class="badge bg-secondary bg-opacity-50 rounded-pill"><%= job.executions_count %></span>
87
88
  <% end %>
89
+ <span class="d-md-none small">Attemp</span>
88
90
  </div>
89
91
  <div class="col d-flex gap-3 align-items-center justify-content-end">
90
92
  <%= tag.span relative_time(job.last_status_at), class: "small" %>
@@ -1,22 +1,22 @@
1
- <div class="break-out bg-light border-bottom py-2 mb-3">
2
- <div class="container-fluid pt-2">
1
+ <div class="border-bottom py-2 mb-3">
2
+ <div class="pt-2">
3
+ <nav aria-label="breadcrumb">
4
+ <ol class="breadcrumb small mb-0">
5
+ <li class="breadcrumb-item"><%= link_to "Jobs", jobs_path %></li>
6
+ <li class="breadcrumb-item active" aria-current="page"><%= tag.code @job.id, class: "text-muted" %></li>
7
+ </ol>
8
+ </nav>
3
9
  <div class="row align-items-center">
4
- <div class="col-5">
5
- <nav aria-label="breadcrumb">
6
- <ol class="breadcrumb small mb-0">
7
- <li class="breadcrumb-item"><%= link_to "Jobs", jobs_path %></li>
8
- <li class="breadcrumb-item active" aria-current="page"><%= tag.code @job.id, class: "text-muted" %></li>
9
- </ol>
10
- </nav>
11
- <h2 class="mb-0"><%= tag.code @job.job_class %></h2>
10
+ <div class="col-md-5">
11
+ <h2 class="mb-2 mb-md-0"><%= tag.code @job.job_class %></h2>
12
12
  </div>
13
- <div class="col-2">
13
+ <div class="col-6 col-md-2">
14
14
  <div class="small text-muted text-uppercase">Queue</div>
15
15
  <div class="badge bg-primary bg-opacity-25 text-dark font-monospace my-2">
16
16
  <%= tag.strong @job.queue_name %>
17
17
  </div>
18
18
  </div>
19
- <div class="col-2">
19
+ <div class="col-6 col-md-2">
20
20
  <div class="small text-muted text-uppercase">Priority</div>
21
21
  <div class="font-monospace fw-bold small my-2"><%= tag.strong @job.priority %></div>
22
22
  </div>
@@ -1,5 +1,5 @@
1
- <div class="bg-light break-out border-bottom">
2
- <h2 class="container-fluid pt-3 pb-2">Processes</h2>
1
+ <div class="border-bottom">
2
+ <h2 class="pt-3 pb-2">Processes</h2>
3
3
  </div>
4
4
 
5
5
  <div class="card my-3" data-live-poll-region="processes">
@@ -1,13 +1,15 @@
1
- <div data-live-poll-region id="filter">
2
- <div class="bg-light break-out">
3
- <h2 class="container-fluid pt-3 pb-2"><%= title %></h2>
1
+ <div data-live-poll-region id="filter" class="">
2
+ <div class="">
3
+ <div class="border-bottom mb-3">
4
+ <h2 class="pt-3 pb-2"><%= title %></h2>
5
+ </div>
4
6
 
5
- <%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "container-fluid") do |form| %>
7
+ <%= form_with(url: "", method: :get, local: true, id: "filter_form", class: "") do |form| %>
6
8
  <%= hidden_field_tag :poll, params[:poll] %>
7
9
  <%= hidden_field_tag :state, params[:state] %>
8
10
  <%= hidden_field_tag :locale, params[:locale] if params[:locale] %>
9
- <div class="d-flex flex-row w-100">
10
- <div class="me-2">
11
+ <div class="d-md-flex flex-row w-100">
12
+ <div class="me-md-2 mb-2 mb-md-0">
11
13
  <%= label_tag "job_queue_filter", "Queue name", class: "visually-hidden" %>
12
14
  <select name="queue_name" id="job_queue_filter" class="form-select form-select-sm">
13
15
  <option value="" <%= "selected='selected'" if params[:queue_name].blank? %>>All queues</option>
@@ -18,7 +20,7 @@
18
20
  </select>
19
21
  </div>
20
22
 
21
- <div class="me-2">
23
+ <div class="me-md-2 mb-2 mb-md-0">
22
24
  <%= label_tag "job_class_filter", "Job name", class: "visually-hidden" %>
23
25
  <select name="job_class" id="job_class_filter" class="form-select form-select-sm">
24
26
  <option value="" <%= "selected='selected'" if params[:job_class].blank? %>>All jobs</option>
@@ -29,7 +31,7 @@
29
31
  </select>
30
32
  </div>
31
33
 
32
- <div class="me-2 flex-fill">
34
+ <div class="me-md-2 mb-2 mb-md-0 flex-fill">
33
35
  <%= label_tag "query", "Search", class: "visually-hidden" %>
34
36
  <%= search_field_tag "query", params[:query], class: "form-control form-control-sm", placeholder: "Search by class, job id, job params, and error text." %>
35
37
  </div>
@@ -45,7 +47,7 @@
45
47
  </div>
46
48
  <% end %>
47
49
 
48
- <ul class="nav nav-tabs bg-light px-3 my-3">
50
+ <ul class="nav nav-tabs my-3">
49
51
  <li class="nav-item">
50
52
  <%= link_to "All", filter.to_params(state: nil), class: "nav-link #{"active" unless params[:state].present?}" %>
51
53
  </li>
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ module ActiveJobExtensions
4
+ module InterruptErrors
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ around_perform do |_job, block|
9
+ raise InterruptError, "Interrupted after starting perform at '#{CurrentThread.execution_interrupted}'" if CurrentThread.execution_interrupted.present?
10
+
11
+ block.call
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -12,6 +12,7 @@ module GoodJob
12
12
  error_on_discard
13
13
  error_on_retry
14
14
  execution
15
+ execution_interrupted
15
16
  ].freeze
16
17
 
17
18
  # @!attribute [rw] cron_at
@@ -44,6 +45,12 @@ module GoodJob
44
45
  # @return [GoodJob::Execution, nil]
45
46
  thread_mattr_accessor :execution
46
47
 
48
+ # @!attribute [rw] execution_interrupted
49
+ # @!scope class
50
+ # Execution Interrupted
51
+ # @return [Boolean, nil]
52
+ thread_mattr_accessor :execution_interrupted
53
+
47
54
  # Resets attributes
48
55
  # @param [Hash] values to assign
49
56
  # @return [void]
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+ module GoodJob
3
+ # Exception raised when a job is interrupted by a SIGKILL or power failure.
4
+ class InterruptError < StandardError
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  module GoodJob
3
3
  # GoodJob gem version.
4
- VERSION = '3.11.0'
4
+ VERSION = '3.12.0'
5
5
  end
data/lib/good_job.rb CHANGED
@@ -9,6 +9,8 @@ require "good_job/adapter"
9
9
  require "active_job/queue_adapters/good_job_adapter"
10
10
  require "good_job/active_job_extensions/batches"
11
11
  require "good_job/active_job_extensions/concurrency"
12
+ require "good_job/interrupt_error"
13
+ require "good_job/active_job_extensions/interrupt_errors"
12
14
  require "good_job/active_job_extensions/notify_options"
13
15
 
14
16
  require "good_job/assignable_connection"
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.11.0
4
+ version: 3.12.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: 2023-02-06 00:00:00.000000000 Z
11
+ date: 2023-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -403,6 +403,7 @@ files:
403
403
  - lib/good_job.rb
404
404
  - lib/good_job/active_job_extensions/batches.rb
405
405
  - lib/good_job/active_job_extensions/concurrency.rb
406
+ - lib/good_job/active_job_extensions/interrupt_errors.rb
406
407
  - lib/good_job/active_job_extensions/notify_options.rb
407
408
  - lib/good_job/adapter.rb
408
409
  - lib/good_job/assignable_connection.rb
@@ -415,6 +416,7 @@ files:
415
416
  - lib/good_job/daemon.rb
416
417
  - lib/good_job/dependencies.rb
417
418
  - lib/good_job/engine.rb
419
+ - lib/good_job/interrupt_error.rb
418
420
  - lib/good_job/job_performer.rb
419
421
  - lib/good_job/log_subscriber.rb
420
422
  - lib/good_job/multi_scheduler.rb