good_job 3.26.2 → 3.27.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 +26 -0
- data/README.md +14 -3
- data/app/controllers/good_job/metrics_controller.rb +10 -0
- data/app/filters/good_job/base_filter.rb +4 -0
- data/app/filters/good_job/jobs_filter.rb +4 -0
- data/app/frontend/good_job/style.css +4 -0
- data/app/models/concerns/good_job/advisory_lockable.rb +2 -7
- data/app/models/good_job/batch_record.rb +1 -0
- data/app/models/good_job/cron_entry.rb +6 -1
- data/app/models/good_job/discrete_execution.rb +1 -0
- data/app/models/good_job/execution.rb +2 -1
- data/app/models/good_job/job.rb +1 -0
- data/app/models/good_job/process.rb +1 -0
- data/app/models/good_job/setting.rb +29 -10
- data/app/views/good_job/shared/_filter.erb +4 -4
- data/app/views/good_job/shared/_navbar.erb +1 -1
- data/config/locales/de.yml +1 -1
- data/config/locales/en.yml +1 -1
- data/config/locales/es.yml +1 -1
- data/config/locales/fr.yml +3 -3
- data/config/locales/it.yml +1 -1
- data/config/locales/nl.yml +1 -1
- data/config/locales/ru.yml +1 -1
- data/config/routes.rb +1 -0
- data/lib/good_job/active_job_extensions/concurrency.rb +66 -17
- data/lib/good_job/version.rb +1 -1
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2b3817133850acc5176086af47b774bcb80a6ead4ee11e1121a10b23b5c7adb0
|
4
|
+
data.tar.gz: 0d8d3412835f37b23122a821d7f1ea1f20120a1e1893d29fadcd6e6b8697bce5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 984697d136c831db59efdd76f8ca3fc820263de06bdcfe7650f685d75ee0ef64cc3e4c39aa2919b84273b15bb15689bc1fb47afce99a597035266c70372a7c65
|
7
|
+
data.tar.gz: f6f5942330d621fd89cc951a0f6731aa87578725f5959c9dfcf13c99252952e9c610385afcb03cc17a8bdeeb6a126d607e18c910b66353c01c941eb8e0a6c515
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,31 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.27.0](https://github.com/bensheldon/good_job/tree/v3.27.0) (2024-03-24)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.26.2...v3.27.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add `enabled_by_default: false` as option for cron configuration [\#1289](https://github.com/bensheldon/good_job/pull/1289) ([bensheldon](https://github.com/bensheldon))
|
10
|
+
- Load metrics for job statuses asynchronously [\#1286](https://github.com/bensheldon/good_job/pull/1286) ([binarygit](https://github.com/binarygit))
|
11
|
+
- Implement throttling options in concurrency extension [\#1270](https://github.com/bensheldon/good_job/pull/1270) ([marckohlbrugge](https://github.com/marckohlbrugge))
|
12
|
+
|
13
|
+
**Fixed bugs:**
|
14
|
+
|
15
|
+
- fix\(ui-dropdown\): use dropdown-end on locales dropdown [\#1296](https://github.com/bensheldon/good_job/pull/1296) ([WailanTirajoh](https://github.com/WailanTirajoh))
|
16
|
+
|
17
|
+
**Closed issues:**
|
18
|
+
|
19
|
+
- Disabling probe [\#1290](https://github.com/bensheldon/good_job/issues/1290)
|
20
|
+
- Set an implicit order on models [\#1242](https://github.com/bensheldon/good_job/issues/1242)
|
21
|
+
|
22
|
+
**Merged pull requests:**
|
23
|
+
|
24
|
+
- docs\(readme\): remove double "using" [\#1295](https://github.com/bensheldon/good_job/pull/1295) ([WailanTirajoh](https://github.com/WailanTirajoh))
|
25
|
+
- Set an implicit order on models [\#1293](https://github.com/bensheldon/good_job/pull/1293) ([mec](https://github.com/mec))
|
26
|
+
- CI: install gems after loading cache, not before [\#1288](https://github.com/bensheldon/good_job/pull/1288) ([bensheldon](https://github.com/bensheldon))
|
27
|
+
- Ensure job execution Advisory Lock query uses bind parameters [\#1287](https://github.com/bensheldon/good_job/pull/1287) ([bensheldon](https://github.com/bensheldon))
|
28
|
+
|
3
29
|
## [v3.26.2](https://github.com/bensheldon/good_job/tree/v3.26.2) (2024-03-15)
|
4
30
|
|
5
31
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.26.1...v3.26.2)
|
data/README.md
CHANGED
@@ -127,7 +127,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
127
127
|
- Because of Rails deferred autoloading, jobs enqueued via the `rails console` may not begin executing on a separate server process until the Rails application is fully initialized by loading a web page once.
|
128
128
|
- Remember, only Active Job's `perform_later` sends jobs to the queue adapter; Active Job's `perform_now` executes the job immediately and does not invoke the queue adapter. GoodJob is not involved in `perform_now` jobs.
|
129
129
|
1. **In Rails' test environment**, by default, GoodJob's Adapter executes jobs `inline` immediately in the current thread.
|
130
|
-
- Future-scheduled jobs can be executed with `GoodJob.perform_inline` using
|
130
|
+
- Future-scheduled jobs can be executed with `GoodJob.perform_inline` using a tool like Timecop or `ActiveSupport::Testing::TimeHelpers`.
|
131
131
|
- Note that Active Job's TestAdapter, which powers test helpers (e.g. `assert_enqueued_with`), may override GoodJob's Adapter in [some configurations](https://github.com/rails/rails/issues/37270).
|
132
132
|
1. **In Rails' production environment**, by default, GoodJob's Adapter enqueues jobs in `external` mode to be executed by a separate execution process:
|
133
133
|
- By default, GoodJob separates job enqueuing from job execution so that jobs can be scaled independently of the web server. Use the GoodJob command-line tool to execute jobs:
|
@@ -541,6 +541,16 @@ class MyJob < ApplicationJob
|
|
541
541
|
# Can be an Integer or Lambda/Proc that is invoked in the context of the job
|
542
542
|
perform_limit: 1,
|
543
543
|
|
544
|
+
# Maximum number of jobs with the concurrency key to be enqueued within
|
545
|
+
# the time period, looking backwards from the current time. Must be an array
|
546
|
+
# with two elements: the number of jobs and the time period.
|
547
|
+
enqueue_throttle: [10, 1.minute],
|
548
|
+
|
549
|
+
# Maximum number of jobs with the concurrency key to be performed within
|
550
|
+
# the time period, looking backwards from the current time. Must be an array
|
551
|
+
# with two elements: the number of jobs and the time period.
|
552
|
+
perform_throttle: [100, 1.hour],
|
553
|
+
|
544
554
|
# Note: Under heavy load, the total number of jobs may exceed the
|
545
555
|
# sum of `enqueue_limit` and `perform_limit` because of race conditions
|
546
556
|
# caused by imperfectly disjunctive states. If you need to constrain
|
@@ -617,9 +627,10 @@ config.good_job.cron = {
|
|
617
627
|
set: { priority: -10 }, # additional Active Job properties; can also be a lambda/proc e.g. `-> { { priority: [1,2].sample } }`
|
618
628
|
description: "Something helpful", # optional description that appears in Dashboard
|
619
629
|
},
|
620
|
-
|
630
|
+
production_task: {
|
621
631
|
cron: "0 0,12 * * *",
|
622
|
-
class: "
|
632
|
+
class: "ProductionJob",
|
633
|
+
enabled_by_default: -> { Rails.env.production? } # Only enable in production, otherwise can be enabled manually through Dashboard
|
623
634
|
},
|
624
635
|
complex_schedule: {
|
625
636
|
class: "ComplexScheduleJob",
|
@@ -16,10 +16,20 @@ module GoodJob
|
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
+
def job_status
|
20
|
+
@filter = JobsFilter.new(params)
|
21
|
+
|
22
|
+
render json: @filter.states.transform_values { |count| number_with_delimiter(count) }
|
23
|
+
end
|
24
|
+
|
19
25
|
private
|
20
26
|
|
21
27
|
def number_to_human(count)
|
22
28
|
helpers.number_to_human(count, **helpers.translate_hash("good_job.number.human.decimal_units"))
|
23
29
|
end
|
30
|
+
|
31
|
+
def number_with_delimiter(count)
|
32
|
+
helpers.number_with_delimiter(count, **helpers.translate_hash('good_job.number.format'))
|
33
|
+
end
|
24
34
|
end
|
25
35
|
end
|
@@ -44,13 +44,8 @@ module GoodJob
|
|
44
44
|
cte_table = Arel::Table.new(:rows)
|
45
45
|
cte_query = original_query.select(primary_key, column).except(:limit)
|
46
46
|
cte_query = cte_query.limit(select_limit) if select_limit
|
47
|
-
cte_type =
|
48
|
-
|
49
|
-
else
|
50
|
-
''
|
51
|
-
end
|
52
|
-
|
53
|
-
composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::SqlLiteral.new([cte_type, "(", cte_query.to_sql, ")"].join(' ')))
|
47
|
+
cte_type = supports_cte_materialization_specifiers? ? :MATERIALIZED : :""
|
48
|
+
composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::UnaryOperation.new(cte_type, cte_query.arel))
|
54
49
|
query = cte_table.project(cte_table[:id])
|
55
50
|
.with(composed_cte)
|
56
51
|
.where(Arel.sql("#{function}(('x' || substr(md5(#{connection.quote(table_name)} || '-' || #{connection.quote_table_name(cte_table.name)}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64)::bigint)"))
|
@@ -7,6 +7,7 @@ module GoodJob
|
|
7
7
|
include AdvisoryLockable
|
8
8
|
|
9
9
|
self.table_name = 'good_job_batches'
|
10
|
+
self.implicit_order_column = 'created_at'
|
10
11
|
|
11
12
|
has_many :jobs, class_name: 'GoodJob::Job', inverse_of: :batch, foreign_key: :batch_id, dependent: nil
|
12
13
|
has_many :executions, class_name: 'GoodJob::Execution', foreign_key: :batch_id, inverse_of: :batch, dependent: nil
|
@@ -74,7 +74,7 @@ module GoodJob # :nodoc:
|
|
74
74
|
def enabled?
|
75
75
|
return true unless GoodJob::Setting.migrated?
|
76
76
|
|
77
|
-
GoodJob::Setting.cron_key_enabled?(key)
|
77
|
+
GoodJob::Setting.cron_key_enabled?(key, default: enabled_by_default?)
|
78
78
|
end
|
79
79
|
|
80
80
|
def enable
|
@@ -132,6 +132,11 @@ module GoodJob # :nodoc:
|
|
132
132
|
|
133
133
|
private
|
134
134
|
|
135
|
+
def enabled_by_default?
|
136
|
+
value = params.fetch(:enabled_by_default, true)
|
137
|
+
value.respond_to?(:call) ? value.call : value
|
138
|
+
end
|
139
|
+
|
135
140
|
def cron
|
136
141
|
params.fetch(:cron)
|
137
142
|
end
|
@@ -5,6 +5,7 @@ module GoodJob # :nodoc:
|
|
5
5
|
include ErrorEvents
|
6
6
|
|
7
7
|
self.table_name = 'good_job_executions'
|
8
|
+
self.implicit_order_column = 'created_at'
|
8
9
|
|
9
10
|
belongs_to :execution, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :discrete_executions, optional: true
|
10
11
|
belongs_to :job, class_name: 'GoodJob::Job', foreign_key: 'active_job_id', primary_key: 'active_job_id', inverse_of: :discrete_executions, optional: true
|
@@ -16,6 +16,7 @@ module GoodJob
|
|
16
16
|
|
17
17
|
self.table_name = 'good_jobs'
|
18
18
|
self.advisory_lockable_column = 'active_job_id'
|
19
|
+
self.implicit_order_column = 'created_at'
|
19
20
|
|
20
21
|
define_model_callbacks :perform
|
21
22
|
define_model_callbacks :perform_unlocked, only: :after
|
@@ -94,7 +95,7 @@ module GoodJob
|
|
94
95
|
# @!method only_scheduled
|
95
96
|
# @!scope class
|
96
97
|
# @return [ActiveRecord::Relation]
|
97
|
-
scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(Time.current)).or(where(scheduled_at: nil)) }
|
98
|
+
scope :only_scheduled, -> { where(arel_table['scheduled_at'].lteq(Arel::Nodes::BindParam.new(ActiveModel::Attribute.with_cast_value("scheduled_at", Time.current, ActiveModel::Type::DateTime.new)))).or(where(scheduled_at: nil)) }
|
98
99
|
|
99
100
|
# Order executions by priority (highest priority first).
|
100
101
|
# @!method priority_ordered
|
data/app/models/good_job/job.rb
CHANGED
@@ -26,6 +26,7 @@ module GoodJob
|
|
26
26
|
|
27
27
|
self.primary_key = 'active_job_id'
|
28
28
|
self.advisory_lockable_column = 'active_job_id'
|
29
|
+
self.implicit_order_column = 'created_at'
|
29
30
|
|
30
31
|
belongs_to :batch, class_name: 'GoodJob::BatchRecord', inverse_of: :jobs, optional: true
|
31
32
|
has_many :executions, -> { order(created_at: :asc) }, class_name: 'GoodJob::Execution', foreign_key: 'active_job_id', inverse_of: :job # rubocop:disable Rails/HasManyOrHasOneDependent
|
@@ -2,29 +2,48 @@
|
|
2
2
|
|
3
3
|
module GoodJob
|
4
4
|
class Setting < BaseRecord
|
5
|
+
CRON_KEYS_ENABLED = "cron_keys_enabled"
|
5
6
|
CRON_KEYS_DISABLED = "cron_keys_disabled"
|
6
7
|
|
7
8
|
self.table_name = 'good_job_settings'
|
9
|
+
self.implicit_order_column = 'created_at'
|
8
10
|
|
9
|
-
def self.cron_key_enabled?(key)
|
10
|
-
|
11
|
-
|
11
|
+
def self.cron_key_enabled?(key, default: true)
|
12
|
+
if default
|
13
|
+
cron_disabled = find_by(key: CRON_KEYS_DISABLED)&.value || []
|
14
|
+
cron_disabled.exclude?(key.to_s)
|
15
|
+
else
|
16
|
+
cron_enabled = find_by(key: CRON_KEYS_ENABLED)&.value || []
|
17
|
+
cron_enabled.include?(key.to_s)
|
18
|
+
end
|
12
19
|
end
|
13
20
|
|
14
21
|
def self.cron_key_enable(key)
|
15
|
-
|
16
|
-
|
22
|
+
enabled_setting = find_or_initialize_by(key: CRON_KEYS_ENABLED) do |record|
|
23
|
+
record.value = []
|
24
|
+
end
|
25
|
+
enabled_setting.value << key unless enabled_setting.value.include?(key)
|
26
|
+
enabled_setting.save!
|
17
27
|
|
18
|
-
|
19
|
-
|
28
|
+
disabled_setting = GoodJob::Setting.find_by(key: CRON_KEYS_DISABLED)
|
29
|
+
return unless disabled_setting&.value&.include?(key.to_s)
|
30
|
+
|
31
|
+
disabled_setting.value.delete(key.to_s)
|
32
|
+
disabled_setting.save!
|
20
33
|
end
|
21
34
|
|
22
35
|
def self.cron_key_disable(key)
|
23
|
-
|
36
|
+
enabled_setting = GoodJob::Setting.find_by(key: CRON_KEYS_ENABLED)
|
37
|
+
if enabled_setting&.value&.include?(key.to_s)
|
38
|
+
enabled_setting.value.delete(key.to_s)
|
39
|
+
enabled_setting.save!
|
40
|
+
end
|
41
|
+
|
42
|
+
disabled_setting = find_or_initialize_by(key: CRON_KEYS_DISABLED) do |record|
|
24
43
|
record.value = []
|
25
44
|
end
|
26
|
-
|
27
|
-
|
45
|
+
disabled_setting.value << key unless disabled_setting.value.include?(key)
|
46
|
+
disabled_setting.save!
|
28
47
|
end
|
29
48
|
end
|
30
49
|
end
|
@@ -47,16 +47,16 @@
|
|
47
47
|
</div>
|
48
48
|
<% end %>
|
49
49
|
|
50
|
-
<ul data-live-poll-region="filter-tabs" class="nav nav-tabs my-3">
|
50
|
+
<ul data-controller="async-values" data-async-values-url-value="<%= metrics_job_status_path %>" data-live-poll-region="filter-tabs" class="nav nav-tabs my-3">
|
51
51
|
<li class="nav-item">
|
52
52
|
<%= link_to t(".all"), filter.to_params(state: nil), class: "nav-link #{"active" unless params[:state].present?}" %>
|
53
53
|
</li>
|
54
54
|
|
55
|
-
<% filter.
|
55
|
+
<% filter.state_names.each do |name| %>
|
56
56
|
<li class="nav-item">
|
57
57
|
<%= link_to filter.to_params(state: name), class: "nav-link #{"active" if params[:state] == name}" do %>
|
58
|
-
<%= t(name, scope: 'good_job.status'
|
59
|
-
<span
|
58
|
+
<%= t(name, scope: 'good_job.status') %>
|
59
|
+
<span data-async-values-target="value", data-async-values-key="<%= name %>" data-async-values-zero-class="bg-secondary" class="badge bg-primary rounded-pill d-none"></span>
|
60
60
|
<% end %>
|
61
61
|
</li>
|
62
62
|
<% end %>
|
@@ -73,7 +73,7 @@
|
|
73
73
|
<%= I18n.locale %>
|
74
74
|
</a>
|
75
75
|
|
76
|
-
<ul class="dropdown-menu" aria-labelledby="localeOptions">
|
76
|
+
<ul class="dropdown-menu dropdown-menu-end min-w-auto" aria-labelledby="localeOptions">
|
77
77
|
<% possible_locales = I18n.available_locales %>
|
78
78
|
<% possible_locales.reject { |locale| locale == I18n.locale }.each do |locale| %>
|
79
79
|
<li><%= link_to locale, url_for(locale: locale), class: "dropdown-item" %></li>
|
data/config/locales/de.yml
CHANGED
@@ -34,7 +34,7 @@ de:
|
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
36
|
confirm_disable: Möchten Sie diesen Cron-Eintrag wirklich deaktivieren?
|
37
|
-
confirm_enable: Möchten Sie diesen Cron-Eintrag wirklich
|
37
|
+
confirm_enable: Möchten Sie diesen Cron-Eintrag wirklich aktivieren?
|
38
38
|
confirm_enqueue: Möchten Sie diesen Cron-Eintrag wirklich in die Warteschlange stellen?
|
39
39
|
disable: Cron-Eintrag deaktivieren
|
40
40
|
enable: Cron-Eintrag aktivieren
|
data/config/locales/en.yml
CHANGED
@@ -34,7 +34,7 @@ en:
|
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
36
|
confirm_disable: Are you sure you want to disable this cron entry?
|
37
|
-
confirm_enable: Are you sure you want to
|
37
|
+
confirm_enable: Are you sure you want to enable this cron entry?
|
38
38
|
confirm_enqueue: Are you sure you want to enqueue this cron entry?
|
39
39
|
disable: Disable cron entry
|
40
40
|
enable: Enable cron entry
|
data/config/locales/es.yml
CHANGED
@@ -34,7 +34,7 @@ es:
|
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
36
|
confirm_disable: "¿Estás seguro que querés deshabilitar esta tarea programada?"
|
37
|
-
confirm_enable: "¿Estás seguro que querés
|
37
|
+
confirm_enable: "¿Estás seguro que querés habilitar esta tarea programada?"
|
38
38
|
confirm_enqueue: "¿Estás seguro que querés encolar esta tarea programada?"
|
39
39
|
disable: Deshabilitar tarea programada
|
40
40
|
enable: Habilitar tarea programada
|
data/config/locales/fr.yml
CHANGED
@@ -33,9 +33,9 @@ fr:
|
|
33
33
|
no_batches_found: Aucun lot trouvé.
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
|
-
confirm_disable: Voulez-vous vraiment désactiver cette entrée cron
|
37
|
-
confirm_enable: Voulez-vous vraiment activer cette entrée cron
|
38
|
-
confirm_enqueue: Voulez-vous vraiment mettre en file d'attente cette entrée cron
|
36
|
+
confirm_disable: Voulez-vous vraiment désactiver cette entrée cron?
|
37
|
+
confirm_enable: Voulez-vous vraiment activer cette entrée cron?
|
38
|
+
confirm_enqueue: Voulez-vous vraiment mettre en file d'attente cette entrée cron?
|
39
39
|
disable: Désactiver l'entrée cron
|
40
40
|
enable: Activer l'entrée cron
|
41
41
|
enqueue: Mettre en file d'attente l'entrée cron maintenant
|
data/config/locales/it.yml
CHANGED
@@ -34,7 +34,7 @@ it:
|
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
36
|
confirm_disable: Sei sicuro di voler disabilitare questa voce cron?
|
37
|
-
confirm_enable: Sei sicuro di voler
|
37
|
+
confirm_enable: Sei sicuro di voler abilita questa voce cron?
|
38
38
|
confirm_enqueue: Sei sicuro di voler mettere in coda questa voce cron?
|
39
39
|
disable: Disabilita voce cron
|
40
40
|
enable: Abilita voce cron
|
data/config/locales/nl.yml
CHANGED
@@ -34,7 +34,7 @@ nl:
|
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
36
|
confirm_disable: Weet u zeker dat u deze cron-vermelding wilt uitschakelen?
|
37
|
-
confirm_enable: Weet u zeker dat u deze cron-invoer wilt
|
37
|
+
confirm_enable: Weet u zeker dat u deze cron-invoer wilt inschakelen?
|
38
38
|
confirm_enqueue: Weet u zeker dat u deze cron-vermelding in de wachtrij wilt plaatsen?
|
39
39
|
disable: Schakel cron-invoer uit
|
40
40
|
enable: Schakel cron-invoer in
|
data/config/locales/ru.yml
CHANGED
@@ -34,7 +34,7 @@ ru:
|
|
34
34
|
cron_entries:
|
35
35
|
actions:
|
36
36
|
confirm_disable: Вы уверены, что хотите отключить эту задачу cron?
|
37
|
-
confirm_enable: Вы уверены, что хотите
|
37
|
+
confirm_enable: Вы уверены, что хотите включить это задание cron?
|
38
38
|
confirm_enqueue: Вы уверены, что хотите поставить эту задачу cron в очередь?
|
39
39
|
disable: Отключить задачу cron
|
40
40
|
enable: Включить задачу cron
|
data/config/routes.rb
CHANGED
@@ -17,6 +17,7 @@ GoodJob::Engine.routes.draw do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
get 'jobs/metrics/primary_nav', to: 'metrics#primary_nav', as: :metrics_primary_nav
|
20
|
+
get 'jobs/metrics/job_status', to: 'metrics#job_status', as: :metrics_job_status
|
20
21
|
|
21
22
|
resources :batches, only: %i[index show]
|
22
23
|
|
@@ -13,6 +13,8 @@ module GoodJob
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
ThrottleExceededError = Class.new(ConcurrencyExceededError)
|
17
|
+
|
16
18
|
module Prepends
|
17
19
|
def deserialize(job_data)
|
18
20
|
super
|
@@ -62,8 +64,13 @@ module GoodJob
|
|
62
64
|
total_limit = nil unless total_limit.present? && (0...Float::INFINITY).cover?(total_limit)
|
63
65
|
end
|
64
66
|
|
67
|
+
perform_throttle = job.class.good_job_concurrency_config[:perform_throttle]
|
68
|
+
perform_throttle = instance_exec(&perform_throttle) if perform_throttle.respond_to?(:call)
|
69
|
+
perform_throttle = nil unless GoodJob::DiscreteExecution.migrated? && perform_throttle.present? && perform_throttle.is_a?(Array) && perform_throttle.size == 2
|
70
|
+
|
65
71
|
limit = perform_limit || total_limit
|
66
|
-
|
72
|
+
throttle = perform_throttle
|
73
|
+
next unless limit || throttle
|
67
74
|
|
68
75
|
key = job.good_job_concurrency_key
|
69
76
|
next if key.blank?
|
@@ -74,9 +81,29 @@ module GoodJob
|
|
74
81
|
end
|
75
82
|
|
76
83
|
GoodJob::Execution.advisory_lock_key(key, function: "pg_advisory_lock") do
|
77
|
-
|
78
|
-
|
79
|
-
|
84
|
+
if limit
|
85
|
+
allowed_active_job_ids = GoodJob::Execution.unfinished.where(concurrency_key: key)
|
86
|
+
.advisory_locked
|
87
|
+
.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC"))
|
88
|
+
.limit(limit).pluck(:active_job_id)
|
89
|
+
# The current job has already been locked and will appear in the previous query
|
90
|
+
raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError unless allowed_active_job_ids.include?(job.job_id)
|
91
|
+
end
|
92
|
+
|
93
|
+
if throttle
|
94
|
+
throttle_limit = throttle[0]
|
95
|
+
throttle_period = throttle[1]
|
96
|
+
|
97
|
+
query = DiscreteExecution.joins(:job)
|
98
|
+
.where(GoodJob::Job.table_name => { concurrency_key: key })
|
99
|
+
.where(DiscreteExecution.arel_table[:created_at].gt(throttle_period.ago))
|
100
|
+
allowed_active_job_ids = query.where(error: nil).or(query.where.not(error: "GoodJob::ActiveJobExtensions::Concurrency::ThrottleExceededError: GoodJob::ActiveJobExtensions::Concurrency::ThrottleExceededError"))
|
101
|
+
.order(created_at: :asc)
|
102
|
+
.limit(throttle_limit)
|
103
|
+
.pluck(:active_job_id)
|
104
|
+
|
105
|
+
raise ThrottleExceededError unless allowed_active_job_ids.include?(job.job_id)
|
106
|
+
end
|
80
107
|
end
|
81
108
|
end
|
82
109
|
end
|
@@ -137,23 +164,45 @@ module GoodJob
|
|
137
164
|
total_limit = nil unless total_limit.present? && (0...Float::INFINITY).cover?(total_limit)
|
138
165
|
end
|
139
166
|
|
167
|
+
enqueue_throttle = job.class.good_job_concurrency_config[:enqueue_throttle]
|
168
|
+
enqueue_throttle = instance_exec(&enqueue_throttle) if enqueue_throttle.respond_to?(:call)
|
169
|
+
enqueue_throttle = nil unless enqueue_throttle.present? && enqueue_throttle.is_a?(Array) && enqueue_throttle.size == 2
|
170
|
+
|
140
171
|
limit = enqueue_limit || total_limit
|
141
|
-
|
172
|
+
throttle = enqueue_throttle
|
173
|
+
return on_enqueue&.call unless limit || throttle
|
142
174
|
|
143
175
|
GoodJob::Execution.advisory_lock_key(key, function: "pg_advisory_lock") do
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
176
|
+
if limit
|
177
|
+
enqueue_concurrency = if enqueue_limit
|
178
|
+
GoodJob::Execution.where(concurrency_key: key).unfinished.advisory_unlocked.count
|
179
|
+
else
|
180
|
+
GoodJob::Execution.where(concurrency_key: key).unfinished.count
|
181
|
+
end
|
182
|
+
|
183
|
+
# The job has not yet been enqueued, so check if adding it will go over the limit
|
184
|
+
if (enqueue_concurrency + 1) > limit
|
185
|
+
logger.info "Aborted enqueue of #{job.class.name} (Job ID: #{job.job_id}) because the concurrency key '#{key}' has reached its enqueue limit of #{limit} #{'job'.pluralize(limit)}"
|
186
|
+
on_abort&.call
|
187
|
+
break
|
188
|
+
end
|
156
189
|
end
|
190
|
+
|
191
|
+
if throttle
|
192
|
+
throttle_limit = throttle[0]
|
193
|
+
throttle_period = throttle[1]
|
194
|
+
enqueued_within_period = GoodJob::Job.where(concurrency_key: key)
|
195
|
+
.where(GoodJob::Job.arel_table[:created_at].gt(throttle_period.ago))
|
196
|
+
.count
|
197
|
+
|
198
|
+
if (enqueued_within_period + 1) > throttle_limit
|
199
|
+
logger.info "Aborted enqueue of #{job.class.name} (Job ID: #{job.job_id}) because the concurrency key '#{key}' has reached its throttle limit of #{limit} #{'job'.pluralize(limit)}"
|
200
|
+
on_abort&.call
|
201
|
+
break
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
on_enqueue&.call
|
157
206
|
end
|
158
207
|
end
|
159
208
|
end
|
data/lib/good_job/version.rb
CHANGED
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.27.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: 2024-03-
|
11
|
+
date: 2024-03-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -206,6 +206,20 @@ dependencies:
|
|
206
206
|
- - ">="
|
207
207
|
- !ruby/object:Gem::Version
|
208
208
|
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: timecop
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
209
223
|
- !ruby/object:Gem::Dependency
|
210
224
|
name: webrick
|
211
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -444,7 +458,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
444
458
|
- !ruby/object:Gem::Version
|
445
459
|
version: '0'
|
446
460
|
requirements: []
|
447
|
-
rubygems_version: 3.5.
|
461
|
+
rubygems_version: 3.5.4
|
448
462
|
signing_key:
|
449
463
|
specification_version: 4
|
450
464
|
summary: A multithreaded, Postgres-based ActiveJob backend for Ruby on Rails
|