good_job 4.22.1 → 4.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +38 -0
- data/README.md +11 -1
- data/app/controllers/good_job/executions_controller.rb +11 -0
- data/app/models/concerns/good_job/filtered_arguments.rb +27 -0
- data/app/models/good_job/concurrency_claim.rb +2 -0
- data/app/models/good_job/cron_entry.rb +9 -11
- data/app/models/good_job/execution.rb +2 -0
- data/app/models/good_job/job.rb +2 -0
- data/app/views/good_job/executions/show.html.erb +11 -0
- data/app/views/good_job/jobs/_executions.erb +2 -6
- data/app/views/good_job/jobs/show.html.erb +1 -1
- data/config/locales/pt-BR.yml +25 -23
- data/config/routes.rb +3 -1
- data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +1 -0
- data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +1 -0
- data/lib/generators/good_job/templates/update/migrations/22_add_index_good_job_executions_on_created_at.rb.erb +12 -0
- data/lib/good_job/adapter.rb +27 -27
- data/lib/good_job/capsule.rb +8 -8
- data/lib/good_job/capsule_tracker.rb +29 -11
- data/lib/good_job/cron_manager.rb +5 -6
- data/lib/good_job/instance_registry.rb +45 -0
- data/lib/good_job/job_performer.rb +2 -1
- data/lib/good_job/notifier.rb +5 -6
- data/lib/good_job/poller.rb +4 -6
- data/lib/good_job/probe_server/simple_handler.rb +3 -0
- data/lib/good_job/probe_server/webrick_handler.rb +2 -0
- data/lib/good_job/probe_server.rb +2 -0
- data/lib/good_job/ractors.rb +2 -0
- data/lib/good_job/scheduler.rb +3 -6
- data/lib/good_job/shared_executor.rb +4 -6
- data/lib/good_job/systemd_service.rb +1 -0
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +13 -2
- metadata +6 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50f42b2481a308af99413d1a89c0b7d3d4c2e07cc48cc29a323c3c42a528e610
|
|
4
|
+
data.tar.gz: a55f5f59b5758614fa38c79b603d4f6298b6e79de40931e05fc4cc4fa0e68f56
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: adacf056f5e11131e0bb704e5b830a11a4a64b0d0a06339c0edaa13b1d8c94bf8b7e734e0df79d3ccf0230e7ceba3da4f220d66aa4eece5dd4dd5e961af294e1
|
|
7
|
+
data.tar.gz: 7b954a6a98b208038575c4d05e6f6e18d219125daf42f66ca8d1e0223f5e18eb1250fd3c608eddeb4f8b78d94ba84f3f141805ccf24f81dedeec0a37bac808ff
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [v4.23.0](https://github.com/bensheldon/good_job/tree/v4.23.0) (2026-10-27)
|
|
4
|
+
|
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.22.1...v4.23.0)
|
|
6
|
+
|
|
7
|
+
**Implemented enhancements:**
|
|
8
|
+
|
|
9
|
+
- Filter sensitive job arguments in the Dashboard [\#1881](https://github.com/bensheldon/good_job/pull/1881) ([bogdan](https://github.com/bogdan))
|
|
10
|
+
|
|
11
|
+
**Fixed bugs:**
|
|
12
|
+
|
|
13
|
+
- Respect enable\_listen\_notify when promoting concurrency claims [\#1892](https://github.com/bensheldon/good_job/pull/1892) ([eidam](https://github.com/eidam))
|
|
14
|
+
- Downgrade lock\_type before releasing advisory lock [\#1883](https://github.com/bensheldon/good_job/pull/1883) ([jmsanders](https://github.com/jmsanders))
|
|
15
|
+
|
|
16
|
+
**Closed issues:**
|
|
17
|
+
|
|
18
|
+
- ConcurrencyClaim sends NOTIFY even when enable\_listen\_notify is false [\#1891](https://github.com/bensheldon/good_job/issues/1891)
|
|
19
|
+
|
|
20
|
+
**Merged pull requests:**
|
|
21
|
+
|
|
22
|
+
- Keep JRuby platform in Gemfile.lock when Bundler re-resolves [\#1893](https://github.com/bensheldon/good_job/pull/1893) ([bensheldon](https://github.com/bensheldon))
|
|
23
|
+
- Make integration specs robust under database load; fix mixed lock strategy specs [\#1890](https://github.com/bensheldon/good_job/pull/1890) ([bensheldon](https://github.com/bensheldon))
|
|
24
|
+
- Pin CI test runners to ubuntu-24.04 [\#1888](https://github.com/bensheldon/good_job/pull/1888) ([bensheldon](https://github.com/bensheldon))
|
|
25
|
+
- Require the concurrent-ruby classes GoodJob uses [\#1885](https://github.com/bensheldon/good_job/pull/1885) ([czj](https://github.com/czj))
|
|
26
|
+
- Add index\_good\_job\_executions\_on\_created\_at [\#1884](https://github.com/bensheldon/good_job/pull/1884) ([clinejj](https://github.com/clinejj))
|
|
27
|
+
- Demo: cap YJIT memory and shrink the Rack::Attack store [\#1880](https://github.com/bensheldon/good_job/pull/1880) ([bensheldon](https://github.com/bensheldon))
|
|
28
|
+
- Demo: enable rbtrace in Puma workers for heap dumps; add sheap [\#1879](https://github.com/bensheldon/good_job/pull/1879) ([bensheldon](https://github.com/bensheldon))
|
|
29
|
+
- Demo: warm up Puma before forking, add Thruster, raise DB pool [\#1878](https://github.com/bensheldon/good_job/pull/1878) ([bensheldon](https://github.com/bensheldon))
|
|
30
|
+
- Return 404 for GET /jobs/mass\_update instead of redirecting [\#1877](https://github.com/bensheldon/good_job/pull/1877) ([bensheldon](https://github.com/bensheldon))
|
|
31
|
+
- Load execution full traces and serialized params on demand [\#1876](https://github.com/bensheldon/good_job/pull/1876) ([bensheldon](https://github.com/bensheldon))
|
|
32
|
+
- Add specs for the demo's Rack::Attack rules [\#1875](https://github.com/bensheldon/good_job/pull/1875) ([bensheldon](https://github.com/bensheldon))
|
|
33
|
+
- Fix demo boot: don't require rails/all in bin/rails [\#1874](https://github.com/bensheldon/good_job/pull/1874) ([bensheldon](https://github.com/bensheldon))
|
|
34
|
+
- Reduce demo app memory and crawler load [\#1873](https://github.com/bensheldon/good_job/pull/1873) ([bensheldon](https://github.com/bensheldon))
|
|
35
|
+
- Fix JRuby flake from inspect in wait\_until predicate expectations [\#1872](https://github.com/bensheldon/good_job/pull/1872) ([bensheldon](https://github.com/bensheldon))
|
|
36
|
+
- Remove Adapter::RUNTIMES so adapters can be garbage collected [\#1870](https://github.com/bensheldon/good_job/pull/1870) ([bensheldon](https://github.com/bensheldon))
|
|
37
|
+
- Add GoodJob.prune to release shut down capsules, schedulers, and notifiers [\#1869](https://github.com/bensheldon/good_job/pull/1869) ([bensheldon](https://github.com/bensheldon))
|
|
38
|
+
- Use resolve\_\[property\] naming for CronEntry configuration resolvers [\#1868](https://github.com/bensheldon/good_job/pull/1868) ([bensheldon](https://github.com/bensheldon))
|
|
39
|
+
- Fix inconsistencies in the Brazilian Portuguese locale [\#1867](https://github.com/bensheldon/good_job/pull/1867) ([tiagoamaro](https://github.com/tiagoamaro))
|
|
40
|
+
|
|
3
41
|
## [v4.22.1](https://github.com/bensheldon/good_job/tree/v4.22.1) (2026-10-13)
|
|
4
42
|
|
|
5
43
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v4.22.0...v4.22.1)
|
data/README.md
CHANGED
|
@@ -44,6 +44,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
|
|
|
44
44
|
- [Dashboard](#dashboard)
|
|
45
45
|
- [API-only Rails applications](#api-only-rails-applications)
|
|
46
46
|
- [Live polling](#live-polling)
|
|
47
|
+
- [Filtering sensitive arguments](#filtering-sensitive-arguments)
|
|
47
48
|
- [Extending dashboard views](#extending-dashboard-views)
|
|
48
49
|
- [Job priority](#job-priority)
|
|
49
50
|
- [Concurrency controls](#concurrency-controls)
|
|
@@ -479,6 +480,15 @@ end
|
|
|
479
480
|
|
|
480
481
|
The Dashboard can be set to automatically refresh by checking "Live Poll" in the Dashboard header, or by setting `?poll=10` with the interval in seconds (default 30 seconds).
|
|
481
482
|
|
|
483
|
+
#### Filtering sensitive arguments
|
|
484
|
+
|
|
485
|
+
The Dashboard hides job arguments the same way Rails hides them from logs:
|
|
486
|
+
|
|
487
|
+
- Hash values whose keys match `Rails.application.config.filter_parameters` (e.g. `password:`) are displayed as `[FILTERED]`.
|
|
488
|
+
- All arguments are displayed as `[FILTERED]` for jobs with `log_arguments` disabled, either for every job with `config.active_job.log_arguments = false` or per job class with `self.log_arguments = false`.
|
|
489
|
+
|
|
490
|
+
Filtering only applies to how arguments are displayed; arguments are still stored unfiltered in the database and are used when the job is performed or retried.
|
|
491
|
+
|
|
482
492
|
#### Extending dashboard views
|
|
483
493
|
|
|
484
494
|
GoodJob exposes some views that are intended to be overridden by placing views in your application:
|
|
@@ -1644,7 +1654,7 @@ end
|
|
|
1644
1654
|
GOOD_JOB_LOCK_STRATEGY=skiplocked
|
|
1645
1655
|
```
|
|
1646
1656
|
|
|
1647
|
-
|
|
1657
|
+
During a rolling deploy, `:hybrid` workers can safely run alongside either `:advisory` or `:skiplocked` workers. But `:advisory` and `:skiplocked` workers must not run at the same time, because neither sees the other's locks and the same job may be performed twice. To move from `:advisory` to `:skiplocked`, deploy `:hybrid` first, wait until no `:advisory` workers remain, and then deploy `:skiplocked`.
|
|
1648
1658
|
|
|
1649
1659
|
#### PgBouncer configuration
|
|
1650
1660
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GoodJob
|
|
4
|
+
# Renders an execution's heavier details on demand so they aren't included in every job page.
|
|
5
|
+
class ExecutionsController < GoodJob::ApplicationController
|
|
6
|
+
def show
|
|
7
|
+
@execution = Execution.find_by!(id: params[:id], active_job_id: params[:job_id])
|
|
8
|
+
render layout: false
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GoodJob
|
|
4
|
+
# Hides sensitive Active Job arguments when displaying Job/Execution records in the Dashboard.
|
|
5
|
+
module FilteredArguments
|
|
6
|
+
# Active Job arguments with sensitive values replaced by +[FILTERED]+.
|
|
7
|
+
# Every argument is filtered when the job class has +log_arguments+ disabled
|
|
8
|
+
# (e.g. +config.active_job.log_arguments = false+); otherwise hash values whose
|
|
9
|
+
# keys match +Rails.application.config.filter_parameters+ are filtered.
|
|
10
|
+
# @return [Array]
|
|
11
|
+
def display_arguments
|
|
12
|
+
arguments = serialized_params.fetch("arguments", [])
|
|
13
|
+
return arguments.map { ActiveSupport::ParameterFilter::FILTERED } unless log_arguments?
|
|
14
|
+
|
|
15
|
+
parameter_filter = ActiveSupport::ParameterFilter.new(Rails.application.config.filter_parameters)
|
|
16
|
+
parameter_filter.filter("arguments" => arguments)["arguments"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def log_arguments?
|
|
22
|
+
active_job_class = job_class&.safe_constantize
|
|
23
|
+
active_job_class = ActiveJob::Base unless active_job_class.respond_to?(:log_arguments?)
|
|
24
|
+
active_job_class.log_arguments?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -172,6 +172,8 @@ module GoodJob
|
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
def notify(job_id)
|
|
175
|
+
return unless GoodJob.configuration.enable_listen_notify
|
|
176
|
+
|
|
175
177
|
queue_name = GoodJob::Job.where(id: job_id).pick(:queue_name)
|
|
176
178
|
GoodJob::Notifier.notify({ queue_name: queue_name }) if queue_name
|
|
177
179
|
end
|
|
@@ -139,8 +139,10 @@ module GoodJob # :nodoc:
|
|
|
139
139
|
job_klass = resolve_job_class
|
|
140
140
|
next unless job_klass.is_a?(Class)
|
|
141
141
|
|
|
142
|
-
configured_job = job_klass.set(
|
|
143
|
-
|
|
142
|
+
configured_job = job_klass.set(resolve_set)
|
|
143
|
+
job_args = resolve_args
|
|
144
|
+
job_kwargs = resolve_kwargs
|
|
145
|
+
job_kwargs.present? ? configured_job.perform_later(*job_args, **job_kwargs) : configured_job.perform_later(*job_args)
|
|
144
146
|
end
|
|
145
147
|
end
|
|
146
148
|
end
|
|
@@ -213,7 +215,8 @@ module GoodJob # :nodoc:
|
|
|
213
215
|
end
|
|
214
216
|
|
|
215
217
|
def resolve_job_class
|
|
216
|
-
value =
|
|
218
|
+
value = job_class
|
|
219
|
+
value = value.call if value.respond_to?(:call)
|
|
217
220
|
value.is_a?(String) ? value.constantize : value
|
|
218
221
|
end
|
|
219
222
|
|
|
@@ -236,22 +239,17 @@ module GoodJob # :nodoc:
|
|
|
236
239
|
schedule if schedule.instance_of?(Fugit::Cron)
|
|
237
240
|
end
|
|
238
241
|
|
|
239
|
-
def
|
|
240
|
-
value = job_class || nil
|
|
241
|
-
value.respond_to?(:call) ? value.call : value
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
def set_value
|
|
242
|
+
def resolve_set
|
|
245
243
|
value = set || {}
|
|
246
244
|
value.respond_to?(:call) ? value.call : value
|
|
247
245
|
end
|
|
248
246
|
|
|
249
|
-
def
|
|
247
|
+
def resolve_args
|
|
250
248
|
value = args || []
|
|
251
249
|
value.respond_to?(:call) ? value.call : value
|
|
252
250
|
end
|
|
253
251
|
|
|
254
|
-
def
|
|
252
|
+
def resolve_kwargs
|
|
255
253
|
value = kwargs || nil
|
|
256
254
|
value.respond_to?(:call) ? value.call : value
|
|
257
255
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
module GoodJob # :nodoc:
|
|
4
4
|
class Execution < BaseRecord
|
|
5
5
|
include ErrorEvents
|
|
6
|
+
include FilteredArguments
|
|
6
7
|
|
|
7
8
|
self.table_name = 'good_job_executions'
|
|
8
9
|
self.implicit_order_column = 'created_at'
|
|
@@ -63,6 +64,7 @@ module GoodJob # :nodoc:
|
|
|
63
64
|
|
|
64
65
|
def display_serialized_params
|
|
65
66
|
serialized_params.merge({
|
|
67
|
+
"arguments" => display_arguments,
|
|
66
68
|
_good_job_execution: attributes.except('serialized_params'),
|
|
67
69
|
})
|
|
68
70
|
end
|
data/app/models/good_job/job.rb
CHANGED
|
@@ -6,6 +6,7 @@ module GoodJob
|
|
|
6
6
|
include AdvisoryLockable
|
|
7
7
|
include Job::Lockable
|
|
8
8
|
include ErrorEvents
|
|
9
|
+
include FilteredArguments
|
|
9
10
|
include Filterable
|
|
10
11
|
include Reportable
|
|
11
12
|
|
|
@@ -622,6 +623,7 @@ module GoodJob
|
|
|
622
623
|
# @return [Hash]
|
|
623
624
|
def display_serialized_params
|
|
624
625
|
serialized_params.merge({
|
|
626
|
+
"arguments" => display_arguments,
|
|
625
627
|
_good_job: attributes.except('serialized_params', 'locktype', 'owns_advisory_lock'),
|
|
626
628
|
})
|
|
627
629
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%= content_tag "turbo-frame", id: dom_id(@execution, :full_trace) do %>
|
|
2
|
+
<div class="small">
|
|
3
|
+
<code class="text-wrap text-break m-0 text-secondary-emphasis">
|
|
4
|
+
<%= safe_join(@execution.error_backtrace || [], tag.br) %>
|
|
5
|
+
</code>
|
|
6
|
+
</div>
|
|
7
|
+
<% end %>
|
|
8
|
+
|
|
9
|
+
<%= content_tag "turbo-frame", id: dom_id(@execution, :serialized_params) do %>
|
|
10
|
+
<%= tag.pre JSON.pretty_generate(truncate_display_value(@execution.display_serialized_params)) %>
|
|
11
|
+
<% end %>
|
|
@@ -58,11 +58,7 @@
|
|
|
58
58
|
</div>
|
|
59
59
|
<% end %>
|
|
60
60
|
<%= tag.div class: "tab-pane fade", id: dom_id(execution, :full_pane), role: "tabpane", aria: { labelledby: dom_id(execution, :full) }, tabindex: 0 do %>
|
|
61
|
-
|
|
62
|
-
<code class="text-wrap text-break m-0 text-secondary-emphasis">
|
|
63
|
-
<%= safe_join(execution.error_backtrace, tag.br) %>
|
|
64
|
-
</code>
|
|
65
|
-
</div>
|
|
61
|
+
<%= content_tag "turbo-frame", "", id: dom_id(execution, :full_trace), src: job_execution_path(@job.id, execution.id), loading: "lazy" %>
|
|
66
62
|
<% end %>
|
|
67
63
|
<% end %>
|
|
68
64
|
<% end %>
|
|
@@ -70,7 +66,7 @@
|
|
|
70
66
|
<% end %>
|
|
71
67
|
<%= render 'good_job/custom_execution_details', execution: execution, job: @job %>
|
|
72
68
|
<%= tag.div id: dom_id(execution, "params"), class: "list-group-item collapse small bg-dark text-light" do %>
|
|
73
|
-
<%=
|
|
69
|
+
<%= content_tag "turbo-frame", "", id: dom_id(execution, :serialized_params), src: job_execution_path(@job.id, execution.id), loading: "lazy" %>
|
|
74
70
|
<% end %>
|
|
75
71
|
<% end %>
|
|
76
72
|
</div>
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
<% end %>
|
|
86
86
|
</h5>
|
|
87
87
|
</div>
|
|
88
|
-
<%= tag.pre @job.
|
|
88
|
+
<%= tag.pre @job.display_arguments.map { |argument| truncate_display_value(argument).inspect }.join(', '), class: 'text-wrap text-break' %>
|
|
89
89
|
|
|
90
90
|
<%= tag.div id: dom_id(@job, "params"), class: "list-group-item collapse small bg-dark text-light" do %>
|
|
91
91
|
<%= tag.pre JSON.pretty_generate(truncate_display_value(@job.display_serialized_params)) %>
|
data/config/locales/pt-BR.yml
CHANGED
|
@@ -37,17 +37,17 @@ pt-BR:
|
|
|
37
37
|
no_batches_found: Nenhum lote encontrado.
|
|
38
38
|
cleaner:
|
|
39
39
|
index:
|
|
40
|
-
all:
|
|
41
|
-
class:
|
|
42
|
-
exception:
|
|
43
|
-
grouped_by_class:
|
|
44
|
-
grouped_by_exception:
|
|
45
|
-
last_1_hour:
|
|
46
|
-
last_24_hours:
|
|
47
|
-
last_3_days:
|
|
48
|
-
last_3_hours:
|
|
49
|
-
last_7_days:
|
|
50
|
-
title:
|
|
40
|
+
all: Tudo
|
|
41
|
+
class: Classe
|
|
42
|
+
exception: Exceção
|
|
43
|
+
grouped_by_class: Descartes agrupados por Classe
|
|
44
|
+
grouped_by_exception: Descartes agrupados por Exceção
|
|
45
|
+
last_1_hour: Última hora
|
|
46
|
+
last_24_hours: Últimas 24 horas
|
|
47
|
+
last_3_days: Últimos 3 dias
|
|
48
|
+
last_3_hours: Últimas 3 horas
|
|
49
|
+
last_7_days: Últimos 7 dias
|
|
50
|
+
title: Limpeza de Descartes
|
|
51
51
|
total: Total
|
|
52
52
|
concurrency_claims:
|
|
53
53
|
states:
|
|
@@ -142,12 +142,12 @@ pt-BR:
|
|
|
142
142
|
reschedule: Reagendar tarefa
|
|
143
143
|
retry: Tentar tarefa novamente
|
|
144
144
|
destroy:
|
|
145
|
-
notice: A tarefa foi
|
|
145
|
+
notice: A tarefa foi excluída.
|
|
146
146
|
discard:
|
|
147
147
|
notice: A tarefa foi descartada.
|
|
148
148
|
executions:
|
|
149
|
-
application_trace:
|
|
150
|
-
full_trace:
|
|
149
|
+
application_trace: Trace da aplicação
|
|
150
|
+
full_trace: Trace completo
|
|
151
151
|
in_queue: na fila
|
|
152
152
|
recovery: recuperação
|
|
153
153
|
runtime: tempo de execução
|
|
@@ -175,7 +175,7 @@ pt-BR:
|
|
|
175
175
|
destroy_all: Excluir tudo
|
|
176
176
|
discard_all: Descartar tudo
|
|
177
177
|
reschedule_all: Reagendar tudo
|
|
178
|
-
retry_all: Tentar novamente
|
|
178
|
+
retry_all: Tentar tudo novamente
|
|
179
179
|
title: Ações
|
|
180
180
|
no_jobs_found: Nenhuma tarefa encontrada.
|
|
181
181
|
toggle_actions: Alternar Ações
|
|
@@ -204,7 +204,7 @@ pt-BR:
|
|
|
204
204
|
job:
|
|
205
205
|
arguments: Argumentos
|
|
206
206
|
attempts: Tentativas
|
|
207
|
-
concurrency_claims:
|
|
207
|
+
concurrency_claims: Reservas de concorrência
|
|
208
208
|
labels: Rótulos
|
|
209
209
|
priority: Prioridade
|
|
210
210
|
queue: Fila
|
|
@@ -230,7 +230,7 @@ pt-BR:
|
|
|
230
230
|
confirm_pause: Tem certeza que deseja pausar %{value}?
|
|
231
231
|
confirm_unpause: Tem certeza que deseja retomar %{value}?
|
|
232
232
|
disabled: O recurso experimental de Pausas do GoodJob está desativado por padrão porque pode degradar o desempenho. Configure para ativar
|
|
233
|
-
job_class: Classe
|
|
233
|
+
job_class: Classe da tarefa
|
|
234
234
|
label: Rótulo
|
|
235
235
|
pause: Pausar
|
|
236
236
|
queue: Fila
|
|
@@ -245,8 +245,8 @@ pt-BR:
|
|
|
245
245
|
chart_title_total: Latência total média em segundos
|
|
246
246
|
execution_latency: Latência de execução
|
|
247
247
|
executions: Execuções
|
|
248
|
-
job_class: Classe
|
|
249
|
-
jobs:
|
|
248
|
+
job_class: Classe da tarefa
|
|
249
|
+
jobs: Tarefas
|
|
250
250
|
no_executions: Nenhuma execução neste intervalo de tempo.
|
|
251
251
|
queue_latency: Latência na fila
|
|
252
252
|
queue_name: Nome da fila
|
|
@@ -267,7 +267,9 @@ pt-BR:
|
|
|
267
267
|
days: "%{count}d"
|
|
268
268
|
hours: "%{count}h"
|
|
269
269
|
minutes: "%{count}m"
|
|
270
|
-
months:
|
|
270
|
+
months:
|
|
271
|
+
one: "~%{count} mês"
|
|
272
|
+
other: "~%{count} meses"
|
|
271
273
|
seconds: "%{count}s"
|
|
272
274
|
years: "~%{count}a"
|
|
273
275
|
chart_bucket_size: 'Granularidade temporal do gráfico: %{size}'
|
|
@@ -317,8 +319,8 @@ pt-BR:
|
|
|
317
319
|
search: Pesquisar
|
|
318
320
|
navbar:
|
|
319
321
|
batches: Lotes
|
|
320
|
-
cleaner:
|
|
321
|
-
cron_schedules:
|
|
322
|
+
cleaner: Limpeza de Descartes
|
|
323
|
+
cron_schedules: Tarefas Programadas
|
|
322
324
|
jobs: Tarefas
|
|
323
325
|
live_poll: Acompanhamento ao Vivo
|
|
324
326
|
name: "GoodJob 👍"
|
|
@@ -332,7 +334,7 @@ pt-BR:
|
|
|
332
334
|
theme: Tema
|
|
333
335
|
pending_migrations: O GoodJob tem migrações pendentes no banco de dados.
|
|
334
336
|
secondary_navbar:
|
|
335
|
-
inspiration: Lembre-se, você também está fazendo
|
|
337
|
+
inspiration: Lembre-se, você também está fazendo um Bom Trabalho!
|
|
336
338
|
last_updated: Última atualização
|
|
337
339
|
status:
|
|
338
340
|
discarded: Descartado
|
data/config/routes.rb
CHANGED
|
@@ -5,7 +5,7 @@ GoodJob::Engine.routes.draw do
|
|
|
5
5
|
|
|
6
6
|
resources :jobs, only: %i[index show destroy] do
|
|
7
7
|
collection do
|
|
8
|
-
get :mass_update, to:
|
|
8
|
+
get :mass_update, to: ->(_env) { raise ActionController::RoutingError, 'Not Found' }
|
|
9
9
|
put :mass_update
|
|
10
10
|
end
|
|
11
11
|
|
|
@@ -15,6 +15,8 @@ GoodJob::Engine.routes.draw do
|
|
|
15
15
|
put :reschedule
|
|
16
16
|
put :retry
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
resources :executions, only: %i[show]
|
|
18
20
|
end
|
|
19
21
|
get 'jobs/metrics/primary_nav', to: 'metrics#primary_nav', as: :metrics_primary_nav
|
|
20
22
|
get 'jobs/metrics/job_status', to: 'metrics#job_status', as: :metrics_job_status
|
|
@@ -114,6 +114,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
|
114
114
|
|
|
115
115
|
add_index :good_job_executions, [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
|
|
116
116
|
add_index :good_job_executions, :scheduled_at, name: :index_good_job_executions_on_scheduled_at
|
|
117
|
+
add_index :good_job_executions, :created_at, name: :index_good_job_executions_on_created_at
|
|
117
118
|
add_index :good_jobs, [:priority, :scheduled_at, :id],
|
|
118
119
|
where: "finished_at IS NULL", name: "index_good_jobs_on_priority_scheduled_at_unfinished"
|
|
119
120
|
add_index :good_jobs, [:queue_name, :scheduled_at, :id],
|
|
@@ -97,6 +97,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
|
|
|
97
97
|
|
|
98
98
|
add_index :good_job_executions, [:active_job_id, :created_at], name: :index_good_job_executions_on_active_job_id_and_created_at
|
|
99
99
|
add_index :good_job_executions, :scheduled_at, name: :index_good_job_executions_on_scheduled_at
|
|
100
|
+
add_index :good_job_executions, :created_at, name: :index_good_job_executions_on_created_at
|
|
100
101
|
add_index :good_jobs, :locked_by_id,
|
|
101
102
|
where: "locked_by_id IS NOT NULL", name: "index_good_jobs_on_locked_by_id"
|
|
102
103
|
add_index :good_jobs, [:priority, :scheduled_at, :id],
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddIndexGoodJobExecutionsOnCreatedAt < ActiveRecord::Migration<%= migration_version %>
|
|
4
|
+
disable_ddl_transaction!
|
|
5
|
+
|
|
6
|
+
def change
|
|
7
|
+
add_index :good_job_executions, :created_at,
|
|
8
|
+
name: :index_good_job_executions_on_created_at,
|
|
9
|
+
algorithm: :concurrently,
|
|
10
|
+
if_not_exists: true
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/good_job/adapter.rb
CHANGED
|
@@ -5,18 +5,7 @@ module GoodJob
|
|
|
5
5
|
# ActiveJob Adapter.
|
|
6
6
|
#
|
|
7
7
|
class Adapter
|
|
8
|
-
|
|
9
|
-
# @!scope class
|
|
10
|
-
# List of all instantiated Adapters in the current process.
|
|
11
|
-
# @return [Array<GoodJob::Adapter>, nil]
|
|
12
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
13
|
-
|
|
14
|
-
# Mutable per-adapter state (the capsule and whether it was started), kept
|
|
15
|
-
# off the adapter instance so that the adapter itself can be made
|
|
16
|
-
# Ractor-shareable. Only accessed from the main Ractor.
|
|
17
|
-
Runtime = Struct.new(:capsule, :async_started)
|
|
18
|
-
RUNTIMES = Concurrent::Map.new
|
|
19
|
-
private_constant :Runtime, :RUNTIMES
|
|
8
|
+
include InstanceRegistry
|
|
20
9
|
|
|
21
10
|
# @param execution_mode [Symbol, nil] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
|
|
22
11
|
#
|
|
@@ -32,13 +21,15 @@ module GoodJob
|
|
|
32
21
|
# -+test+: +:inline+
|
|
33
22
|
# - +production+ and all other environments: +:external+
|
|
34
23
|
#
|
|
35
|
-
|
|
24
|
+
# @param _capsule [GoodJob::Capsule, nil] Capsule to use instead of {GoodJob.capsule}. An adapter with
|
|
25
|
+
# its own capsule cannot be made Ractor-shareable.
|
|
26
|
+
def initialize(execution_mode: nil, _capsule: nil) # rubocop:disable Lint/UnderscorePrefixedVariableName
|
|
36
27
|
@_execution_mode_override = execution_mode
|
|
37
28
|
GoodJob::Configuration.validate_execution_mode(@_execution_mode_override) if @_execution_mode_override
|
|
38
|
-
|
|
29
|
+
@_capsule = _capsule
|
|
39
30
|
|
|
40
31
|
start_async if GoodJob.async_ready?
|
|
41
|
-
|
|
32
|
+
register_instance
|
|
42
33
|
end
|
|
43
34
|
|
|
44
35
|
# Enqueues the ActiveJob job to be performed.
|
|
@@ -255,9 +246,7 @@ module GoodJob
|
|
|
255
246
|
# * A positive number will wait that many seconds before stopping any remaining active threads.
|
|
256
247
|
# @return [void]
|
|
257
248
|
def shutdown(timeout: NONE)
|
|
258
|
-
|
|
259
|
-
runtime.capsule&.shutdown(timeout: timeout)
|
|
260
|
-
runtime.async_started = false
|
|
249
|
+
capsule.shutdown(timeout: timeout)
|
|
261
250
|
end
|
|
262
251
|
|
|
263
252
|
# This adapter's execution mode
|
|
@@ -292,40 +281,51 @@ module GoodJob
|
|
|
292
281
|
def start_async
|
|
293
282
|
return unless execute_async?
|
|
294
283
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
runtime.capsule.lower_thread_priority = true if GoodJob.configuration.lower_thread_priority.in?([true, nil])
|
|
298
|
-
runtime.async_started = true
|
|
284
|
+
capsule.start
|
|
285
|
+
capsule.lower_thread_priority = true if GoodJob.configuration.lower_thread_priority.in?([true, nil])
|
|
299
286
|
end
|
|
300
287
|
|
|
301
288
|
# Whether the async executors are running
|
|
302
289
|
# @return [Boolean]
|
|
303
290
|
def async_started?
|
|
304
|
-
GoodJob::Ractors.on_main(self) {
|
|
291
|
+
GoodJob::Ractors.on_main(self) { capsule.running? }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Adapters are retained until Rails has initialized so that async adapters can be started.
|
|
295
|
+
# @return [Boolean]
|
|
296
|
+
def prunable?
|
|
297
|
+
GoodJob.async_ready?
|
|
305
298
|
end
|
|
306
299
|
|
|
307
300
|
private
|
|
308
301
|
|
|
302
|
+
# The capsule is resolved on each use (rather than stored) so the adapter
|
|
303
|
+
# remains Ractor-shareable. Only call from the main Ractor.
|
|
304
|
+
# @return [GoodJob::Capsule]
|
|
305
|
+
def capsule
|
|
306
|
+
@_capsule || GoodJob.capsule
|
|
307
|
+
end
|
|
308
|
+
|
|
309
309
|
# The capsule is not Ractor-shareable, so it is always used on the main Ractor.
|
|
310
310
|
def create_capsule_thread(state)
|
|
311
311
|
job_state = GoodJob::Ractors.main? ? state : GoodJob::Ractors.make_shareable(state.dup)
|
|
312
|
-
GoodJob::Ractors.on_main(self) {
|
|
312
|
+
GoodJob::Ractors.on_main(self) { capsule&.create_thread(job_state) }
|
|
313
313
|
end
|
|
314
314
|
|
|
315
315
|
def register_tracker
|
|
316
316
|
GoodJob::Ractors.on_main(self) do
|
|
317
|
-
tracker =
|
|
317
|
+
tracker = capsule.tracker
|
|
318
318
|
tracker.register
|
|
319
319
|
tracker.id_for_lock
|
|
320
320
|
end
|
|
321
321
|
end
|
|
322
322
|
|
|
323
323
|
def unregister_tracker
|
|
324
|
-
GoodJob::Ractors.on_main(self) {
|
|
324
|
+
GoodJob::Ractors.on_main(self) { capsule.tracker.unregister }
|
|
325
325
|
end
|
|
326
326
|
|
|
327
327
|
def tracker_id_for_lock
|
|
328
|
-
GoodJob::Ractors.on_main(self) {
|
|
328
|
+
GoodJob::Ractors.on_main(self) { capsule.tracker.id_for_lock }
|
|
329
329
|
end
|
|
330
330
|
|
|
331
331
|
# Active Record does not support +insert_all+ outside of the main Ractor.
|
data/lib/good_job/capsule.rb
CHANGED
|
@@ -5,11 +5,7 @@ module GoodJob
|
|
|
5
5
|
# a {GoodJob::Scheduler}, {GoodJob::Poller}, {GoodJob::Notifier}, and {GoodJob::CronManager}.
|
|
6
6
|
# GoodJob creates a default capsule on initialization.
|
|
7
7
|
class Capsule
|
|
8
|
-
|
|
9
|
-
# @!scope class
|
|
10
|
-
# List of all instantiated Capsules in the current process.
|
|
11
|
-
# @return [Array<GoodJob::Capsule>, nil]
|
|
12
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
8
|
+
include InstanceRegistry
|
|
13
9
|
|
|
14
10
|
delegate :register, :renew, :unregister, :id_for_lock, to: :@tracker, prefix: :_tracker
|
|
15
11
|
|
|
@@ -26,7 +22,7 @@ module GoodJob
|
|
|
26
22
|
@tracker = GoodJob::CapsuleTracker.new(executor: @shared_executor)
|
|
27
23
|
@lower_thread_priority = nil
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
register_instance
|
|
30
26
|
end
|
|
31
27
|
|
|
32
28
|
# Start the capsule once. After a shutdown, {#restart} must be used to start again.
|
|
@@ -37,6 +33,8 @@ module GoodJob
|
|
|
37
33
|
@mutex.synchronize do
|
|
38
34
|
return unless startable?(force: force)
|
|
39
35
|
|
|
36
|
+
register_instance
|
|
37
|
+
|
|
40
38
|
@notifier = GoodJob::Notifier.new(enable_listening: configuration.enable_listen_notify, capsule: self, executor: @shared_executor)
|
|
41
39
|
@poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
|
|
42
40
|
@multi_scheduler = GoodJob::MultiScheduler.from_configuration(configuration, capsule: self, warm_cache_on_initialize: true).tap do |multischeduler|
|
|
@@ -116,12 +114,14 @@ module GoodJob
|
|
|
116
114
|
@multi_scheduler&.lower_thread_priority = value
|
|
117
115
|
end
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
# Configuration for this capsule, falling back to the global configuration.
|
|
118
|
+
# @return [GoodJob::Configuration]
|
|
121
119
|
def configuration
|
|
122
120
|
@configuration || GoodJob.configuration
|
|
123
121
|
end
|
|
124
122
|
|
|
123
|
+
private
|
|
124
|
+
|
|
125
125
|
def startable?(force: false)
|
|
126
126
|
!@started_at && (@startable || force)
|
|
127
127
|
end
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "concurrent/errors"
|
|
4
|
+
require "concurrent/scheduled_task"
|
|
5
|
+
|
|
3
6
|
module GoodJob # :nodoc:
|
|
4
7
|
# CapsuleTracker save a record in the database and periodically refreshes it. The intention is to
|
|
5
8
|
# create a heartbeat that can be used to determine whether a capsule/process is still active
|
|
@@ -16,11 +19,7 @@ module GoodJob # :nodoc:
|
|
|
16
19
|
# @return [Integer]
|
|
17
20
|
attr_reader :advisory_locks
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
# @!scope class
|
|
21
|
-
# List of all instantiated CapsuleTrackers in the current process.
|
|
22
|
-
# @return [Array<GoodJob::CapsuleTracker>, nil]
|
|
23
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
22
|
+
include InstanceRegistry
|
|
24
23
|
|
|
25
24
|
# @param executor [Concurrent::AbstractExecutorService] The executor to use for refreshing the process record.
|
|
26
25
|
def initialize(executor: Concurrent.global_io_executor)
|
|
@@ -32,10 +31,9 @@ module GoodJob # :nodoc:
|
|
|
32
31
|
@record_id = SecureRandom.uuid
|
|
33
32
|
@record = nil
|
|
34
33
|
@refresh_task = nil
|
|
34
|
+
@pid = ::Process.pid
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
self.class.instances << self
|
|
36
|
+
register_instance
|
|
39
37
|
end
|
|
40
38
|
|
|
41
39
|
# The UUID to use for locking. May be nil if the process is not registered or is unusable/expired.
|
|
@@ -57,10 +55,18 @@ module GoodJob # :nodoc:
|
|
|
57
55
|
value
|
|
58
56
|
end
|
|
59
57
|
|
|
58
|
+
# Whether the tracker has no active registrations and can be removed from {.instances}.
|
|
59
|
+
# It is re-added to {.instances} when it is registered again.
|
|
60
|
+
# @return [Boolean]
|
|
61
|
+
def prunable?
|
|
62
|
+
@locks.zero?
|
|
63
|
+
end
|
|
64
|
+
|
|
60
65
|
# The expected UUID of the process for use in inspection.
|
|
61
66
|
# Use {#id_for_lock} if using this as a lock key.
|
|
62
67
|
# @return [String]
|
|
63
68
|
def process_id
|
|
69
|
+
reset if forked?
|
|
64
70
|
@record_id
|
|
65
71
|
end
|
|
66
72
|
|
|
@@ -75,6 +81,7 @@ module GoodJob # :nodoc:
|
|
|
75
81
|
# where with_connection always returns a newly borrowed connection.
|
|
76
82
|
GoodJob::Process.lease_connection if with_advisory_lock && advisory_lock_connection.nil?
|
|
77
83
|
|
|
84
|
+
register_instance
|
|
78
85
|
synchronize(with_connection: with_advisory_lock) do
|
|
79
86
|
if with_advisory_lock && !advisory_locked?
|
|
80
87
|
if @record
|
|
@@ -112,9 +119,10 @@ module GoodJob # :nodoc:
|
|
|
112
119
|
if with_advisory_lock && @advisory_locked
|
|
113
120
|
active = advisory_locked?
|
|
114
121
|
if !active || matches_advisory_locked_connection?(advisory_lock_connection)
|
|
115
|
-
|
|
116
|
-
#
|
|
122
|
+
# Move remaining jobs to heartbeat liveness before releasing the lock; otherwise
|
|
123
|
+
# another process's cleanup can see an unlocked advisory record and release them.
|
|
117
124
|
@record&.update(lock_type: nil) if @locks > 1
|
|
125
|
+
@record.advisory_unlock(connection: advisory_lock_connection) if active
|
|
118
126
|
@advisory_locked = false
|
|
119
127
|
@advisory_locked_connection = nil
|
|
120
128
|
end
|
|
@@ -213,7 +221,14 @@ module GoodJob # :nodoc:
|
|
|
213
221
|
synchronize { ns_reset }
|
|
214
222
|
end
|
|
215
223
|
|
|
224
|
+
# Reset lazily after a fork (rather than with an ActiveSupport::ForkTracker callback)
|
|
225
|
+
# so that the tracker is not retained after it is no longer used.
|
|
226
|
+
def forked?
|
|
227
|
+
@pid != ::Process.pid
|
|
228
|
+
end
|
|
229
|
+
|
|
216
230
|
def ns_reset
|
|
231
|
+
@pid = ::Process.pid
|
|
217
232
|
@record_id = SecureRandom.uuid
|
|
218
233
|
@record = nil
|
|
219
234
|
@advisory_locked = false
|
|
@@ -235,7 +250,10 @@ module GoodJob # :nodoc:
|
|
|
235
250
|
elsif @mutex.owned?
|
|
236
251
|
yield
|
|
237
252
|
else
|
|
238
|
-
@mutex.synchronize
|
|
253
|
+
@mutex.synchronize do
|
|
254
|
+
ns_reset if forked?
|
|
255
|
+
yield
|
|
256
|
+
end
|
|
239
257
|
end
|
|
240
258
|
end
|
|
241
259
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "concurrent/errors"
|
|
4
|
+
require "concurrent/future"
|
|
3
5
|
require "concurrent/hash"
|
|
4
6
|
require "concurrent/scheduled_task"
|
|
5
7
|
require "fugit"
|
|
@@ -9,11 +11,7 @@ module GoodJob # :nodoc:
|
|
|
9
11
|
# CronManagers enqueue jobs on a repeating schedule.
|
|
10
12
|
#
|
|
11
13
|
class CronManager
|
|
12
|
-
|
|
13
|
-
# @!scope class
|
|
14
|
-
# List of all instantiated CronManagers in the current process.
|
|
15
|
-
# @return [Array<GoodJob::CronManager>, nil]
|
|
16
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
14
|
+
include InstanceRegistry
|
|
17
15
|
|
|
18
16
|
# Task observer for cron task
|
|
19
17
|
# @param time [Time]
|
|
@@ -41,11 +39,12 @@ module GoodJob # :nodoc:
|
|
|
41
39
|
@graceful_restart_period = graceful_restart_period
|
|
42
40
|
|
|
43
41
|
start if start_on_initialize
|
|
44
|
-
|
|
42
|
+
register_instance
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
# Schedule tasks that will enqueue jobs based on their schedule
|
|
48
46
|
def start
|
|
47
|
+
register_instance
|
|
49
48
|
ActiveSupport::Notifications.instrument("cron_manager_start.good_job", cron_entries: cron_entries) do
|
|
50
49
|
@running = true
|
|
51
50
|
graceful_restart = graceful_restart?
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent/array"
|
|
4
|
+
|
|
5
|
+
module GoodJob # :nodoc:
|
|
6
|
+
# Tracks instances of a class in a class-level +instances+ registry so they can be
|
|
7
|
+
# shut down or restarted together (e.g. by {GoodJob.shutdown} and {GoodJob.restart}).
|
|
8
|
+
# Instances remain in the registry after they are shut down so that they can be
|
|
9
|
+
# restarted; use +prune+ to remove them so they can be garbage collected.
|
|
10
|
+
module InstanceRegistry
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
# @!attribute [r] instances
|
|
15
|
+
# @!scope class
|
|
16
|
+
# List of all registered instances in the current process.
|
|
17
|
+
# @return [Array]
|
|
18
|
+
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class_methods do
|
|
22
|
+
# Removes prunable (e.g. shut down) instances from the registry so they can be garbage collected.
|
|
23
|
+
# Instances are re-registered if they are started again.
|
|
24
|
+
# @return [Array] The pruned instances.
|
|
25
|
+
def prune
|
|
26
|
+
pruned = instances.select(&:prunable?)
|
|
27
|
+
instances.delete_if { |instance| pruned.include?(instance) }
|
|
28
|
+
pruned
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether the instance can be removed from the registry.
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def prunable?
|
|
35
|
+
shutdown?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def register_instance
|
|
41
|
+
instances = self.class.instances
|
|
42
|
+
instances << self unless instances.include?(self)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'concurrent/delay'
|
|
4
|
+
require "concurrent/set"
|
|
4
5
|
|
|
5
6
|
module GoodJob
|
|
6
7
|
#
|
|
@@ -32,7 +33,7 @@ module GoodJob
|
|
|
32
33
|
def next
|
|
33
34
|
active_job_id = nil
|
|
34
35
|
@capsule.tracker.register do
|
|
35
|
-
job_query.perform_with_lock(lock_id: @capsule.tracker.id_for_lock, parsed_queues: parsed_queues, queue_select_limit:
|
|
36
|
+
job_query.perform_with_lock(lock_id: @capsule.tracker.id_for_lock, parsed_queues: parsed_queues, queue_select_limit: @capsule.configuration.queue_select_limit, lock_strategy: @capsule.configuration.lock_strategy) do |execution|
|
|
36
37
|
@metrics.touch_check_queue_at
|
|
37
38
|
|
|
38
39
|
if execution
|
data/lib/good_job/notifier.rb
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'active_support/core_ext/module/attribute_accessors_per_thread'
|
|
4
|
+
require "concurrent/array"
|
|
4
5
|
require 'concurrent/atomic/atomic_boolean'
|
|
6
|
+
require "concurrent/atomic/atomic_fixnum"
|
|
7
|
+
require "concurrent/atomic/event"
|
|
5
8
|
require "concurrent/scheduled_task"
|
|
6
9
|
require "good_job/notifier/process_heartbeat"
|
|
7
10
|
|
|
@@ -39,11 +42,7 @@ module GoodJob # :nodoc:
|
|
|
39
42
|
PG::Error
|
|
40
43
|
].freeze
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
# @!scope class
|
|
44
|
-
# List of all instantiated Notifiers in the current process.
|
|
45
|
-
# @return [Array<GoodJob::Notifier>, nil]
|
|
46
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
45
|
+
include InstanceRegistry
|
|
47
46
|
|
|
48
47
|
# @!attribute [rw] connection
|
|
49
48
|
# @!scope class
|
|
@@ -85,7 +84,6 @@ module GoodJob # :nodoc:
|
|
|
85
84
|
@last_keepalive_time = Time.current
|
|
86
85
|
|
|
87
86
|
start
|
|
88
|
-
self.class.instances << self
|
|
89
87
|
end
|
|
90
88
|
|
|
91
89
|
def running?
|
|
@@ -192,6 +190,7 @@ module GoodJob # :nodoc:
|
|
|
192
190
|
delegate :synchronize, to: :@monitor
|
|
193
191
|
|
|
194
192
|
def start
|
|
193
|
+
register_instance
|
|
195
194
|
synchronize do
|
|
196
195
|
return if @running.true?
|
|
197
196
|
|
data/lib/good_job/poller.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "concurrent/array"
|
|
3
4
|
require 'concurrent/atomic/atomic_boolean'
|
|
5
|
+
require "concurrent/timer_task"
|
|
4
6
|
|
|
5
7
|
module GoodJob # :nodoc:
|
|
6
8
|
#
|
|
@@ -14,11 +16,7 @@ module GoodJob # :nodoc:
|
|
|
14
16
|
run_now: true,
|
|
15
17
|
}.freeze
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
# @!scope class
|
|
19
|
-
# List of all instantiated Pollers in the current process.
|
|
20
|
-
# @return [Array<GoodJob::Poller>, nil]
|
|
21
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
19
|
+
include InstanceRegistry
|
|
22
20
|
|
|
23
21
|
# Creates GoodJob::Poller from a GoodJob::Configuration instance.
|
|
24
22
|
# @param configuration [GoodJob::Configuration]
|
|
@@ -40,7 +38,6 @@ module GoodJob # :nodoc:
|
|
|
40
38
|
@timer_options[:execution_interval] = poll_interval if poll_interval.present?
|
|
41
39
|
|
|
42
40
|
create_timer
|
|
43
|
-
self.class.instances << self
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
# Tests whether the timer is running.
|
|
@@ -99,6 +96,7 @@ module GoodJob # :nodoc:
|
|
|
99
96
|
|
|
100
97
|
# @return [void]
|
|
101
98
|
def create_timer
|
|
99
|
+
register_instance
|
|
102
100
|
return if @timer_options[:execution_interval] <= 0
|
|
103
101
|
|
|
104
102
|
@timer = Concurrent::TimerTask.new(@timer_options) do
|
data/lib/good_job/ractors.rb
CHANGED
data/lib/good_job/scheduler.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "concurrent/executor/thread_pool_executor"
|
|
4
4
|
require "concurrent/executor/timer_set"
|
|
5
|
+
require "concurrent/future"
|
|
5
6
|
require "concurrent/scheduled_task"
|
|
6
7
|
require "concurrent/utility/processor_counter"
|
|
7
8
|
|
|
@@ -32,11 +33,7 @@ module GoodJob # :nodoc:
|
|
|
32
33
|
# In CRuby, this sets the thread quantum to ~12.5ms ( 100ms * 2^(-3) ).
|
|
33
34
|
LOW_THREAD_PRIORITY = -3
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
# @!scope class
|
|
37
|
-
# List of all instantiated Schedulers in the current process.
|
|
38
|
-
# @return [Array<GoodJob::Scheduler>, nil]
|
|
39
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
36
|
+
include InstanceRegistry
|
|
40
37
|
|
|
41
38
|
# Human readable name of the scheduler that includes configuration values.
|
|
42
39
|
# @return [String]
|
|
@@ -74,7 +71,6 @@ module GoodJob # :nodoc:
|
|
|
74
71
|
|
|
75
72
|
create_executor
|
|
76
73
|
warm_cache if warm_cache_on_initialize
|
|
77
|
-
self.class.instances << self
|
|
78
74
|
end
|
|
79
75
|
|
|
80
76
|
# Tests whether the scheduler is running.
|
|
@@ -270,6 +266,7 @@ module GoodJob # :nodoc:
|
|
|
270
266
|
|
|
271
267
|
# @return [void]
|
|
272
268
|
def create_executor
|
|
269
|
+
register_instance
|
|
273
270
|
instrument("scheduler_create_pool", { performer_name: performer.name, max_threads: @executor_options[:max_threads] }) do
|
|
274
271
|
@timer_set = TimerSet.new
|
|
275
272
|
@executor = ThreadPoolExecutor.new(@executor_options)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'concurrent/executor/executor_service'
|
|
4
|
+
require "concurrent/executor/thread_pool_executor"
|
|
4
5
|
|
|
5
6
|
module GoodJob
|
|
6
7
|
class SharedExecutor
|
|
@@ -9,18 +10,14 @@ module GoodJob
|
|
|
9
10
|
|
|
10
11
|
MAX_THREADS = 2
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
# @!scope class
|
|
14
|
-
# List of all instantiated SharedExecutor in the current process.
|
|
15
|
-
# @return [Array<GoodJob::SharedExecutor>, nil]
|
|
16
|
-
cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
|
|
13
|
+
include InstanceRegistry
|
|
17
14
|
|
|
18
15
|
attr_reader :executor
|
|
19
16
|
|
|
20
17
|
def initialize
|
|
21
18
|
@mutex = Mutex.new
|
|
22
19
|
|
|
23
|
-
|
|
20
|
+
register_instance
|
|
24
21
|
end
|
|
25
22
|
|
|
26
23
|
def post(*args, &task)
|
|
@@ -75,6 +72,7 @@ module GoodJob
|
|
|
75
72
|
private
|
|
76
73
|
|
|
77
74
|
def create_executor
|
|
75
|
+
register_instance
|
|
78
76
|
@executor = Concurrent::ThreadPoolExecutor.new(
|
|
79
77
|
# On JRuby, a Java thread pool with an unbounded queue never grows beyond min_threads (except to a single
|
|
80
78
|
# thread from 0), and the Notifier's listen loop occupies a thread for as long as it runs.
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "good_job/version"
|
|
|
7
7
|
require_relative "good_job/engine"
|
|
8
8
|
require_relative "good_job/ractors"
|
|
9
9
|
|
|
10
|
+
require_relative "good_job/instance_registry"
|
|
10
11
|
require_relative "good_job/adapter"
|
|
11
12
|
require_relative "good_job/adapter/inline_buffer"
|
|
12
13
|
require_relative "active_job/queue_adapters/good_job_adapter"
|
|
@@ -249,6 +250,16 @@ module GoodJob
|
|
|
249
250
|
Capsule.instances.all?(&:shutdown?)
|
|
250
251
|
end
|
|
251
252
|
|
|
253
|
+
# Removes shut down instances (e.g. {GoodJob::Capsule}, {GoodJob::Scheduler}, {GoodJob::Notifier}) from their
|
|
254
|
+
# class-level registries so they can be garbage collected. Shut down instances are otherwise retained so that
|
|
255
|
+
# {.restart} can restart them; pruned instances will not be restarted by {.restart} unless started again directly.
|
|
256
|
+
# This is useful in processes that repeatedly create and shut down capsules, such as test suites.
|
|
257
|
+
# @return [void]
|
|
258
|
+
def self.prune
|
|
259
|
+
[Adapter, Capsule, CronManager, Notifier, Poller, Scheduler, SharedExecutor, CapsuleTracker].each(&:prune)
|
|
260
|
+
nil
|
|
261
|
+
end
|
|
262
|
+
|
|
252
263
|
# Stops and restarts executing jobs.
|
|
253
264
|
# GoodJob does its work in pools of background threads.
|
|
254
265
|
# When forking processes you should shut down these background threads before forking, and restart them after forking.
|
|
@@ -387,8 +398,8 @@ module GoodJob
|
|
|
387
398
|
# For use in tests/CI to validate GoodJob is up-to-date.
|
|
388
399
|
# @return [Boolean]
|
|
389
400
|
def self.migrated?
|
|
390
|
-
GoodJob::
|
|
391
|
-
connection.
|
|
401
|
+
GoodJob::Execution.connection_pool.with_connection do |connection|
|
|
402
|
+
connection.index_name_exists?(GoodJob::Execution.table_name, :index_good_job_executions_on_created_at)
|
|
392
403
|
end
|
|
393
404
|
end
|
|
394
405
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: good_job
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ben Sheldon
|
|
@@ -275,6 +275,7 @@ files:
|
|
|
275
275
|
- app/controllers/good_job/batches_controller.rb
|
|
276
276
|
- app/controllers/good_job/cleaner_controller.rb
|
|
277
277
|
- app/controllers/good_job/cron_entries_controller.rb
|
|
278
|
+
- app/controllers/good_job/executions_controller.rb
|
|
278
279
|
- app/controllers/good_job/frontends_controller.rb
|
|
279
280
|
- app/controllers/good_job/jobs_controller.rb
|
|
280
281
|
- app/controllers/good_job/metrics_controller.rb
|
|
@@ -307,6 +308,7 @@ files:
|
|
|
307
308
|
- app/models/concerns/good_job/advisory_lockable.rb
|
|
308
309
|
- app/models/concerns/good_job/error_events.rb
|
|
309
310
|
- app/models/concerns/good_job/filterable.rb
|
|
311
|
+
- app/models/concerns/good_job/filtered_arguments.rb
|
|
310
312
|
- app/models/concerns/good_job/reportable.rb
|
|
311
313
|
- app/models/good_job/active_record_parent_class.rb
|
|
312
314
|
- app/models/good_job/base_record.rb
|
|
@@ -332,6 +334,7 @@ files:
|
|
|
332
334
|
- app/views/good_job/cleaner/index.html.erb
|
|
333
335
|
- app/views/good_job/cron_entries/index.html.erb
|
|
334
336
|
- app/views/good_job/cron_entries/show.html.erb
|
|
337
|
+
- app/views/good_job/executions/show.html.erb
|
|
335
338
|
- app/views/good_job/jobs/_executions.erb
|
|
336
339
|
- app/views/good_job/jobs/_table.erb
|
|
337
340
|
- app/views/good_job/jobs/index.html.erb
|
|
@@ -394,6 +397,7 @@ files:
|
|
|
394
397
|
- lib/generators/good_job/templates/update/migrations/19_remove_redundant_good_jobs_indexes.rb.erb
|
|
395
398
|
- lib/generators/good_job/templates/update/migrations/20_reorder_good_jobs_cron_index.rb.erb
|
|
396
399
|
- lib/generators/good_job/templates/update/migrations/21_create_good_job_concurrency_claims.rb.erb
|
|
400
|
+
- lib/generators/good_job/templates/update/migrations/22_add_index_good_job_executions_on_created_at.rb.erb
|
|
397
401
|
- lib/generators/good_job/update_generator.rb
|
|
398
402
|
- lib/good_job.rb
|
|
399
403
|
- lib/good_job/active_job_extensions/batches.rb
|
|
@@ -416,6 +420,7 @@ files:
|
|
|
416
420
|
- lib/good_job/daemon.rb
|
|
417
421
|
- lib/good_job/dependencies.rb
|
|
418
422
|
- lib/good_job/engine.rb
|
|
423
|
+
- lib/good_job/instance_registry.rb
|
|
419
424
|
- lib/good_job/interrupt_error.rb
|
|
420
425
|
- lib/good_job/interrupted_error.rb
|
|
421
426
|
- lib/good_job/job_performer.rb
|