good_job 3.0.1 → 3.2.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 +57 -0
- data/README.md +18 -2
- data/app/controllers/good_job/jobs_controller.rb +1 -1
- data/app/views/good_job/jobs/_executions.erb +1 -1
- data/app/views/good_job/jobs/_table.erb +2 -2
- data/app/views/good_job/jobs/show.html.erb +1 -1
- data/app/views/good_job/shared/_filter.erb +1 -1
- data/app/views/good_job/shared/_navbar.erb +15 -3
- data/config/locales/en.yml +19 -0
- data/config/locales/es.yml +19 -0
- data/config/locales/nl.yml +19 -0
- data/config/locales/ru.yml +19 -0
- data/lib/good_job/active_job_extensions/concurrency.rb +20 -0
- data/lib/good_job/job_performer.rb +1 -1
- data/lib/good_job/version.rb +1 -1
- data/lib/models/good_job/cron_entry.rb +4 -2
- data/lib/models/good_job/execution.rb +68 -6
- data/lib/models/good_job/job.rb +57 -7
- data/lib/models/good_job/process.rb +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ecbb75f2764a72686acb96367389abb1900321b5cf1292d6522aa8094c68451
|
4
|
+
data.tar.gz: 43a5305a9f2610c1544d6e52c61f0b338771b07d92d6493fed0058ad33bbc045
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e637579c190a3f3d1aba6c02f6a71e2193d9e5c4bb449a3d4d9571093150a25a1c293ac3f5c541d2fae09aeffff771692fa7ef58bc8ed672f61a2c9d35a7e74
|
7
|
+
data.tar.gz: bc669b4d1628f7ff4b885787c65a38a57ad020aa168c3ae85f0c6bb26c9f0e19b64947682b0c99cf3eb28c2d660e1d79ee6095000a72340f09229d1679fa5dfe
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,62 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.2.0](https://github.com/bensheldon/good_job/tree/v3.2.0) (2022-07-12)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.1.0...v3.2.0)
|
6
|
+
|
7
|
+
**Merged pull requests:**
|
8
|
+
|
9
|
+
- Ordered queue handling by workers [\#665](https://github.com/bensheldon/good_job/pull/665) ([jrochkind](https://github.com/jrochkind))
|
10
|
+
|
11
|
+
## [v3.1.0](https://github.com/bensheldon/good_job/tree/v3.1.0) (2022-07-11)
|
12
|
+
|
13
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.0.2...v3.1.0)
|
14
|
+
|
15
|
+
**Implemented enhancements:**
|
16
|
+
|
17
|
+
- 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))
|
18
|
+
|
19
|
+
**Fixed bugs:**
|
20
|
+
|
21
|
+
- 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))
|
22
|
+
|
23
|
+
**Closed issues:**
|
24
|
+
|
25
|
+
- How to suppress repetitive logs in development? [\#658](https://github.com/bensheldon/good_job/issues/658)
|
26
|
+
- 500 Internal Server Error Exception in web interface trying to view running jobs [\#656](https://github.com/bensheldon/good_job/issues/656)
|
27
|
+
- Cron schedule page in dashboard not showing kwargs [\#608](https://github.com/bensheldon/good_job/issues/608)
|
28
|
+
- Paralelism x database connections [\#569](https://github.com/bensheldon/good_job/issues/569)
|
29
|
+
|
30
|
+
**Merged pull requests:**
|
31
|
+
|
32
|
+
- Show job/cron/process counts in the Navbar [\#663](https://github.com/bensheldon/good_job/pull/663) ([bensheldon](https://github.com/bensheldon))
|
33
|
+
- Dequeing should be first-in first-out [\#651](https://github.com/bensheldon/good_job/pull/651) ([jrochkind](https://github.com/jrochkind))
|
34
|
+
|
35
|
+
## [v3.0.2](https://github.com/bensheldon/good_job/tree/v3.0.2) (2022-07-10)
|
36
|
+
|
37
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.0.1...v3.0.2)
|
38
|
+
|
39
|
+
**Fixed bugs:**
|
40
|
+
|
41
|
+
- Copy forward concurrency key value when retrying a job, rather than regenerating it [\#622](https://github.com/bensheldon/good_job/issues/622)
|
42
|
+
- All concurrency controlled jobs throw exceptions and are rescheduled if they are called using perform\_now [\#591](https://github.com/bensheldon/good_job/issues/591)
|
43
|
+
|
44
|
+
**Closed issues:**
|
45
|
+
|
46
|
+
- Queue config not respecting limits [\#659](https://github.com/bensheldon/good_job/issues/659)
|
47
|
+
- UI engine does not work without explicit require [\#646](https://github.com/bensheldon/good_job/issues/646)
|
48
|
+
- Should `:inline` adapter mode retry jobs? [\#611](https://github.com/bensheldon/good_job/issues/611)
|
49
|
+
- Error Job Not Preserved [\#594](https://github.com/bensheldon/good_job/issues/594)
|
50
|
+
- Jobs never get run... [\#516](https://github.com/bensheldon/good_job/issues/516)
|
51
|
+
- Release GoodJob 3.0 [\#507](https://github.com/bensheldon/good_job/issues/507)
|
52
|
+
- Improve security of Gem releases [\#422](https://github.com/bensheldon/good_job/issues/422)
|
53
|
+
|
54
|
+
**Merged pull requests:**
|
55
|
+
|
56
|
+
- Preserve initial concurrency key when retrying jobs [\#657](https://github.com/bensheldon/good_job/pull/657) ([bensheldon](https://github.com/bensheldon))
|
57
|
+
- Add Dashboard troubleshooting note to explicitly require the engine [\#654](https://github.com/bensheldon/good_job/pull/654) ([bensheldon](https://github.com/bensheldon))
|
58
|
+
- Removes wrong parentheses [\#653](https://github.com/bensheldon/good_job/pull/653) ([esasse](https://github.com/esasse))
|
59
|
+
|
3
60
|
## [v3.0.1](https://github.com/bensheldon/good_job/tree/v3.0.1) (2022-07-02)
|
4
61
|
|
5
62
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.0.0...v3.0.1)
|
data/README.md
CHANGED
@@ -370,6 +370,16 @@ GoodJob includes a Dashboard as a mountable `Rails::Engine`.
|
|
370
370
|
|
371
371
|
See more at [Monitor and preserve worked jobs](#monitor-and-preserve-worked-jobs)
|
372
372
|
|
373
|
+
**Troubleshooting the Dashboard:** Some applications are unable to autoload the Goodjob Engine. To work around this, explicitly require the Engine at the top of your `config/application.rb` file, immediately after Rails is required and before Bundler requires the Rails' groups.
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
# config/application.rb
|
377
|
+
require_relative 'boot'
|
378
|
+
require 'rails/all'
|
379
|
+
require 'good_job/engine' # <= Add this line
|
380
|
+
# ...
|
381
|
+
```
|
382
|
+
|
373
383
|
#### API-only Rails applications
|
374
384
|
|
375
385
|
API-only Rails applications may not have all of the required Rack middleware for the GoodJob Dashboard to function. To re-add the middlware:
|
@@ -655,7 +665,7 @@ By default, GoodJob creates a single thread execution pool that will execute job
|
|
655
665
|
|
656
666
|
A pool is configured with the following syntax `<participating_queues>:<thread_count>`:
|
657
667
|
|
658
|
-
- `<participating_queues>`: either `queue1,queue2` (only those queues), `*` (all) or `-queue1,queue2` (all except those queues).
|
668
|
+
- `<participating_queues>`: either `queue1,queue2` (only those queues), `+queue1,queue2` (only those queues, and processed in order), `*` (all) or `-queue1,queue2` (all except those queues).
|
659
669
|
- `<thread_count>`: a count overriding for this specific pool the global `max-threads`.
|
660
670
|
|
661
671
|
Pool configurations are separated with a semicolon (;) in the `queues` configuration
|
@@ -673,6 +683,12 @@ By default, GoodJob creates a single thread execution pool that will execute job
|
|
673
683
|
- `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing`, with up to 2 threads.
|
674
684
|
- `*`: execute jobs on any queue, with up to 5 threads (as configured by `--max-threads=5`).
|
675
685
|
|
686
|
+
When a pool is performing jobs from multiple queues, jobs will be performed from specified queues, ordered by priority and creation time. To perform jobs from queues in the queues' given order, use the `+` modifier. In this example, jobs in `batch_processing` will be performed only when there are no jobs in `transactional_messages`:
|
687
|
+
|
688
|
+
```bash
|
689
|
+
bundle exec good_job --queues="+transactional_messages,batch_processing"
|
690
|
+
```
|
691
|
+
|
676
692
|
Configuration can be injected by environment variables too:
|
677
693
|
|
678
694
|
```bash
|
@@ -713,7 +729,7 @@ Each GoodJob execution thread requires its own database connection that is autom
|
|
713
729
|
|
714
730
|
```yaml
|
715
731
|
# config/database.yml
|
716
|
-
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + 3 +
|
732
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", 5).to_i + 3 + ENV.fetch("GOOD_JOB_MAX_THREADS", 5).to_i %>
|
717
733
|
```
|
718
734
|
|
719
735
|
To calculate the total number of the database connections you'll need:
|
@@ -38,7 +38,7 @@
|
|
38
38
|
</div>
|
39
39
|
<% end %>
|
40
40
|
<div>
|
41
|
-
<%= tag.pre JSON.pretty_generate(execution.
|
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
|
-
|
16
|
+
Parameters
|
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.
|
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">
|
@@ -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
|
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
|
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
|
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">
|
data/config/locales/en.yml
CHANGED
@@ -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: "."
|
data/config/locales/es.yml
CHANGED
@@ -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: ","
|
data/config/locales/nl.yml
CHANGED
@@ -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: ","
|
data/config/locales/ru.yml
CHANGED
@@ -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: ","
|
@@ -10,8 +10,18 @@ module GoodJob
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
+
module Prepends
|
14
|
+
def deserialize(job_data)
|
15
|
+
super
|
16
|
+
self.good_job_concurrency_key = job_data['good_job_concurrency_key']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
13
20
|
included do
|
21
|
+
prepend Prepends
|
22
|
+
|
14
23
|
class_attribute :good_job_concurrency_config, instance_accessor: false, default: {}
|
24
|
+
attr_writer :good_job_concurrency_key
|
15
25
|
|
16
26
|
around_enqueue do |job, block|
|
17
27
|
# Don't attempt to enforce concurrency limits with other queue adapters.
|
@@ -27,6 +37,8 @@ module GoodJob
|
|
27
37
|
(total_limit.present? && (0...Float::INFINITY).cover?(total_limit))
|
28
38
|
next(block.call) unless has_limit
|
29
39
|
|
40
|
+
# Only generate the concurrency key on the initial enqueue in case it is dynamic
|
41
|
+
job.good_job_concurrency_key ||= job._good_job_concurrency_key
|
30
42
|
key = job.good_job_concurrency_key
|
31
43
|
next(block.call) if key.blank?
|
32
44
|
|
@@ -80,7 +92,15 @@ module GoodJob
|
|
80
92
|
end
|
81
93
|
end
|
82
94
|
|
95
|
+
# Existing or dynamically generated concurrency key
|
96
|
+
# @return [Object] concurrency key
|
83
97
|
def good_job_concurrency_key
|
98
|
+
@good_job_concurrency_key || _good_job_concurrency_key
|
99
|
+
end
|
100
|
+
|
101
|
+
# Generates the concurrency key from the configuration
|
102
|
+
# @return [Object] concurrency key
|
103
|
+
def _good_job_concurrency_key
|
84
104
|
key = self.class.good_job_concurrency_config[:key]
|
85
105
|
return if key.blank?
|
86
106
|
|
@@ -24,7 +24,7 @@ module GoodJob
|
|
24
24
|
# Perform the next eligible job
|
25
25
|
# @return [Object, nil] Returns job result or +nil+ if no job was found
|
26
26
|
def next
|
27
|
-
job_query.perform_with_advisory_lock
|
27
|
+
job_query.perform_with_advisory_lock(parsed_queues: parsed_queues)
|
28
28
|
end
|
29
29
|
|
30
30
|
# Tests whether this performer should be used in GoodJob's current state.
|
data/lib/good_job/version.rb
CHANGED
@@ -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
|
@@ -29,15 +29,21 @@ module GoodJob
|
|
29
29
|
# not match.
|
30
30
|
# - +{ include: Array<String> }+ indicates the listed queue names should
|
31
31
|
# match.
|
32
|
+
# - +{ include: Array<String>, ordered_queues: true }+ indicates the listed
|
33
|
+
# queue names should match, and dequeue should respect queue order.
|
32
34
|
# @example
|
33
35
|
# GoodJob::Execution.queue_parser('-queue1,queue2')
|
34
36
|
# => { exclude: [ 'queue1', 'queue2' ] }
|
35
37
|
def self.queue_parser(string)
|
36
38
|
string = string.presence || '*'
|
37
39
|
|
38
|
-
|
40
|
+
case string.first
|
41
|
+
when '-'
|
39
42
|
exclude_queues = true
|
40
43
|
string = string[1..-1]
|
44
|
+
when '+'
|
45
|
+
ordered_queues = true
|
46
|
+
string = string[1..-1]
|
41
47
|
end
|
42
48
|
|
43
49
|
queues = string.split(',').map(&:strip)
|
@@ -46,6 +52,11 @@ module GoodJob
|
|
46
52
|
{ all: true }
|
47
53
|
elsif exclude_queues
|
48
54
|
{ exclude: queues }
|
55
|
+
elsif ordered_queues
|
56
|
+
{
|
57
|
+
include: queues,
|
58
|
+
ordered_queues: true,
|
59
|
+
}
|
49
60
|
else
|
50
61
|
{ include: queues }
|
51
62
|
end
|
@@ -88,6 +99,42 @@ module GoodJob
|
|
88
99
|
# @return [ActiveRecord::Relation]
|
89
100
|
scope :priority_ordered, -> { order('priority DESC NULLS LAST') }
|
90
101
|
|
102
|
+
# Order jobs by created_at, for first-in first-out
|
103
|
+
# @!method creation_ordered
|
104
|
+
# @!scope class
|
105
|
+
# @return [ActiveRecord:Relation]
|
106
|
+
scope :creation_ordered, -> { order('created_at ASC') }
|
107
|
+
|
108
|
+
# Order jobs for de-queueing
|
109
|
+
# @!method dequeueing_ordered
|
110
|
+
# @!scope class
|
111
|
+
# @param parsed_queues [Hash]
|
112
|
+
# optional output of .queue_parser, parsed queues, will be used for
|
113
|
+
# ordered queues.
|
114
|
+
# @return [ActiveRecord::Relation]
|
115
|
+
scope :dequeueing_ordered, (lambda do |parsed_queues|
|
116
|
+
relation = self
|
117
|
+
relation = relation.queue_ordered(parsed_queues[:include]) if parsed_queues && parsed_queues[:ordered_queues] && parsed_queues[:include]
|
118
|
+
relation = relation.priority_ordered.creation_ordered
|
119
|
+
|
120
|
+
relation
|
121
|
+
end)
|
122
|
+
|
123
|
+
# Order jobs in order of queues in array param
|
124
|
+
# @!method queue_ordered
|
125
|
+
# @!scope class
|
126
|
+
# @param queues [Array<string] ordered names of queues
|
127
|
+
# @return [ActiveRecord::Relation]
|
128
|
+
scope :queue_ordered, (lambda do |queues|
|
129
|
+
clauses = queues.map.with_index do |queue_name, index|
|
130
|
+
"WHEN queue_name = '#{queue_name}' THEN #{index}"
|
131
|
+
end
|
132
|
+
|
133
|
+
order(
|
134
|
+
Arel.sql("(CASE #{clauses.join(' ')} ELSE #{queues.length} END)")
|
135
|
+
)
|
136
|
+
end)
|
137
|
+
|
91
138
|
# Order jobs by scheduled or created (oldest first).
|
92
139
|
# @!method schedule_ordered
|
93
140
|
# @!scope class
|
@@ -153,8 +200,8 @@ module GoodJob
|
|
153
200
|
# return value for the job's +#perform+ method, and the exception the job
|
154
201
|
# raised, if any (if the job raised, then the second array entry will be
|
155
202
|
# +nil+). If there were no jobs to execute, returns +nil+.
|
156
|
-
def self.perform_with_advisory_lock
|
157
|
-
unfinished.
|
203
|
+
def self.perform_with_advisory_lock(parsed_queues: nil)
|
204
|
+
unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(unlock_session: true) do |executions|
|
158
205
|
execution = executions.first
|
159
206
|
break if execution.blank?
|
160
207
|
break :unlocked unless execution&.executable?
|
@@ -279,7 +326,9 @@ module GoodJob
|
|
279
326
|
# @return [Symbol]
|
280
327
|
def status
|
281
328
|
if finished_at.present?
|
282
|
-
if error.present?
|
329
|
+
if error.present? && retried_good_job_id.present?
|
330
|
+
:retried
|
331
|
+
elsif error.present? && retried_good_job_id.nil?
|
283
332
|
:discarded
|
284
333
|
else
|
285
334
|
:finished
|
@@ -297,8 +346,20 @@ module GoodJob
|
|
297
346
|
end
|
298
347
|
end
|
299
348
|
|
349
|
+
# Return formatted serialized_params for display in the dashboard
|
350
|
+
# @return [Hash]
|
351
|
+
def display_serialized_params
|
352
|
+
serialized_params.merge({
|
353
|
+
_good_job: attributes.except('serialized_params', 'locktype', 'owns_advisory_lock'),
|
354
|
+
})
|
355
|
+
end
|
356
|
+
|
300
357
|
def running?
|
301
|
-
|
358
|
+
if has_attribute?(:locktype)
|
359
|
+
self['locktype'].present?
|
360
|
+
else
|
361
|
+
advisory_locked?
|
362
|
+
end
|
302
363
|
end
|
303
364
|
|
304
365
|
def number
|
@@ -314,7 +375,7 @@ module GoodJob
|
|
314
375
|
def queue_latency
|
315
376
|
now = Time.zone.now
|
316
377
|
expected_start = scheduled_at || created_at
|
317
|
-
actual_start = performed_at || now
|
378
|
+
actual_start = performed_at || finished_at || now
|
318
379
|
|
319
380
|
actual_start - expected_start unless expected_start >= now
|
320
381
|
end
|
@@ -330,6 +391,7 @@ module GoodJob
|
|
330
391
|
serialized_params.deep_dup
|
331
392
|
.tap do |job_data|
|
332
393
|
job_data["provider_job_id"] = id
|
394
|
+
job_data["good_job_concurrency_key"] = concurrency_key if concurrency_key
|
333
395
|
end
|
334
396
|
end
|
335
397
|
|
data/lib/models/good_job/job.rb
CHANGED
@@ -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
|
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
|
-
|
76
|
-
|
77
|
-
|
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 =
|
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
|
-
|
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
|
4
|
+
version: 3.2.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-
|
11
|
+
date: 2022-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|