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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +18 -0
- data/app/models/good_job/execution.rb +20 -25
- data/app/views/good_job/batches/_jobs.erb +9 -7
- data/app/views/good_job/batches/_table.erb +31 -10
- data/app/views/good_job/batches/index.html.erb +4 -0
- data/app/views/good_job/batches/show.html.erb +6 -4
- data/app/views/good_job/cron_entries/index.html.erb +2 -2
- data/app/views/good_job/jobs/_executions.erb +11 -12
- data/app/views/good_job/jobs/_table.erb +10 -8
- data/app/views/good_job/jobs/show.html.erb +12 -12
- data/app/views/good_job/processes/index.html.erb +2 -2
- data/app/views/good_job/shared/_filter.erb +11 -9
- data/lib/good_job/active_job_extensions/interrupt_errors.rb +16 -0
- data/lib/good_job/current_thread.rb +7 -0
- data/lib/good_job/interrupt_error.rb +6 -0
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eeb15f65ff801785a0df591dec95a0e044a4f5d7e61e6b2483003c4b2afd8cb3
|
4
|
+
data.tar.gz: e5c631765c1303d1fee0597313bbcb5d3844b85ad49600f2609c83149885872d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
319
|
-
|
318
|
+
result = GoodJob::CurrentThread.within do |current_thread|
|
319
|
+
current_thread.reset
|
320
|
+
current_thread.execution = self
|
320
321
|
|
321
|
-
|
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"
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
<div class="col"
|
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,7 +1,7 @@
|
|
1
|
-
<div class="
|
2
|
-
<div class="
|
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
|
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
|
-
|
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">
|
@@ -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="
|
2
|
-
<div class="
|
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
|
-
<
|
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,13 +1,15 @@
|
|
1
|
-
<div data-live-poll-region id="filter">
|
2
|
-
<div class="
|
3
|
-
<
|
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: "
|
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
|
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]
|
data/lib/good_job/version.rb
CHANGED
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.
|
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-
|
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
|