good_job 4.1.0 → 4.1.1
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 +41 -0
- data/app/charts/good_job/performance_index_chart.rb +1 -1
- data/app/charts/good_job/performance_show_chart.rb +1 -1
- data/app/controllers/good_job/application_controller.rb +1 -1
- data/app/controllers/good_job/frontends_controller.rb +6 -2
- data/app/controllers/good_job/performance_controller.rb +1 -1
- data/app/frontend/good_job/icons.svg +79 -0
- data/app/frontend/good_job/style.css +5 -0
- data/app/helpers/good_job/icons_helper.rb +8 -5
- data/app/models/concerns/good_job/advisory_lockable.rb +17 -7
- data/app/models/concerns/good_job/reportable.rb +8 -12
- data/app/models/good_job/batch.rb +10 -5
- data/app/models/good_job/batch_record.rb +18 -15
- data/app/models/good_job/discrete_execution.rb +6 -59
- data/app/models/good_job/execution.rb +59 -4
- data/app/models/good_job/execution_result.rb +6 -6
- data/app/models/good_job/job.rb +567 -12
- data/app/views/good_job/batches/_jobs.erb +1 -1
- data/app/views/good_job/batches/_table.erb +1 -1
- data/app/views/good_job/jobs/index.html.erb +1 -1
- data/app/views/layouts/good_job/application.html.erb +7 -7
- data/config/brakeman.ignore +75 -0
- data/config/locales/de.yml +49 -49
- data/config/locales/es.yml +14 -14
- data/config/routes.rb +3 -3
- data/lib/good_job/active_job_extensions/concurrency.rb +105 -98
- data/lib/good_job/adapter/inline_buffer.rb +73 -0
- data/lib/good_job/adapter.rb +59 -53
- data/lib/good_job/configuration.rb +3 -4
- data/lib/good_job/cron_manager.rb +1 -3
- data/lib/good_job/current_thread.rb +4 -4
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +6 -5
- metadata +6 -20
- data/app/models/good_job/base_execution.rb +0 -605
- data/app/views/good_job/shared/icons/_arrow_clockwise.html.erb +0 -5
- data/app/views/good_job/shared/icons/_check.html.erb +0 -5
- data/app/views/good_job/shared/icons/_circle_half.html.erb +0 -4
- data/app/views/good_job/shared/icons/_clock.html.erb +0 -5
- data/app/views/good_job/shared/icons/_dash_circle.html.erb +0 -5
- data/app/views/good_job/shared/icons/_dots.html.erb +0 -3
- data/app/views/good_job/shared/icons/_eject.html.erb +0 -4
- data/app/views/good_job/shared/icons/_exclamation.html.erb +0 -5
- data/app/views/good_job/shared/icons/_globe.html.erb +0 -3
- data/app/views/good_job/shared/icons/_info.html.erb +0 -4
- data/app/views/good_job/shared/icons/_moon_stars_fill.html.erb +0 -5
- data/app/views/good_job/shared/icons/_pause.html.erb +0 -4
- data/app/views/good_job/shared/icons/_play.html.erb +0 -4
- data/app/views/good_job/shared/icons/_skip_forward.html.erb +0 -4
- data/app/views/good_job/shared/icons/_stop.html.erb +0 -4
- data/app/views/good_job/shared/icons/_sun_fill.html.erb +0 -4
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"ignored_warnings": [
|
|
3
|
+
{
|
|
4
|
+
"warning_type": "Dynamic Render Path",
|
|
5
|
+
"warning_code": 15,
|
|
6
|
+
"fingerprint": "041ae0dc908151bac0ef0952c625f0dce3a05d2c01a710397a613ef10083f7ae",
|
|
7
|
+
"check_name": "Render",
|
|
8
|
+
"message": "Render path contains parameter value",
|
|
9
|
+
"file": "app/controllers/good_job/frontends_controller.rb",
|
|
10
|
+
"line": 47,
|
|
11
|
+
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
|
12
|
+
"code": "render(file => (self.class.js_modules[params[:id].to_sym] or raise(ActionController::RoutingError, \"Not Found\")), {})",
|
|
13
|
+
"render_path": null,
|
|
14
|
+
"location": {
|
|
15
|
+
"type": "method",
|
|
16
|
+
"class": "GoodJob::FrontendsController",
|
|
17
|
+
"method": "module"
|
|
18
|
+
},
|
|
19
|
+
"user_input": "params[:id].to_sym",
|
|
20
|
+
"confidence": "Weak",
|
|
21
|
+
"cwe_id": [
|
|
22
|
+
22
|
|
23
|
+
],
|
|
24
|
+
"note": "Files are explicitly enumerated in the array"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"warning_type": "Dynamic Render Path",
|
|
28
|
+
"warning_code": 15,
|
|
29
|
+
"fingerprint": "b0c2888c9b217671d90d0141b49b036af3b2a70c63b02968cc97ae2052c86272",
|
|
30
|
+
"check_name": "Render",
|
|
31
|
+
"message": "Render path contains parameter value",
|
|
32
|
+
"file": "app/controllers/good_job/frontends_controller.rb",
|
|
33
|
+
"line": 41,
|
|
34
|
+
"link": "https://brakemanscanner.org/docs/warning_types/dynamic_render_path/",
|
|
35
|
+
"code": "render(file => ({ :css => ({ :bootstrap => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"bootstrap\", \"bootstrap.min.css\"), :style => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"style.css\") }), :js => ({ :bootstrap => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"bootstrap\", \"bootstrap.bundle.min.js\"), :chartjs => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"chartjs\", \"chart.min.js\"), :es_module_shims => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"es_module_shims.js\"), :rails_ujs => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"vendor\", \"rails_ujs.js\") }), :svg => ({ :icons => GoodJob::Engine.root.join(\"app\", \"frontend\", \"good_job\", \"icons.svg\") }) }.dig(params[:format].to_sym, params[:id].to_sym) or raise(ActionController::RoutingError, \"Not Found\")), {})",
|
|
36
|
+
"render_path": null,
|
|
37
|
+
"location": {
|
|
38
|
+
"type": "method",
|
|
39
|
+
"class": "GoodJob::FrontendsController",
|
|
40
|
+
"method": "static"
|
|
41
|
+
},
|
|
42
|
+
"user_input": "params[:id].to_sym",
|
|
43
|
+
"confidence": "Weak",
|
|
44
|
+
"cwe_id": [
|
|
45
|
+
22
|
|
46
|
+
],
|
|
47
|
+
"note": "Files are explicitly enumerated in the array"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"warning_type": "SQL Injection",
|
|
51
|
+
"warning_code": 0,
|
|
52
|
+
"fingerprint": "c837568c590d9608a8bb9927b31b9597aaacc72053b6482e1a54bd02aa0dd2d7",
|
|
53
|
+
"check_name": "SQL",
|
|
54
|
+
"message": "Possible SQL injection",
|
|
55
|
+
"file": "app/models/good_job/job.rb",
|
|
56
|
+
"line": 140,
|
|
57
|
+
"link": "https://brakemanscanner.org/docs/warning_types/sql_injection/",
|
|
58
|
+
"code": "Arel.sql(\"(CASE #{queues.map.with_index do\n sanitize_sql_array([\"WHEN queue_name = ? THEN ?\", queue_name, index])\n end.join(\" \")} ELSE #{queues.size} END)\")",
|
|
59
|
+
"render_path": null,
|
|
60
|
+
"location": {
|
|
61
|
+
"type": "method",
|
|
62
|
+
"class": "Job",
|
|
63
|
+
"method": null
|
|
64
|
+
},
|
|
65
|
+
"user_input": "queues.map.with_index do\n sanitize_sql_array([\"WHEN queue_name = ? THEN ?\", queue_name, index])\n end.join(\" \")",
|
|
66
|
+
"confidence": "Medium",
|
|
67
|
+
"cwe_id": [
|
|
68
|
+
89
|
|
69
|
+
],
|
|
70
|
+
"note": "Developer provided value, queue_name, is sanitized."
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"updated": "2024-07-18 18:05:56 -0700",
|
|
74
|
+
"brakeman_version": "6.1.2"
|
|
75
|
+
}
|
data/config/locales/de.yml
CHANGED
|
@@ -10,17 +10,17 @@ de:
|
|
|
10
10
|
retry: Wiederholen
|
|
11
11
|
batches:
|
|
12
12
|
index:
|
|
13
|
-
older_batches: Ältere
|
|
14
|
-
title:
|
|
13
|
+
older_batches: Ältere Batches
|
|
14
|
+
title: Batches
|
|
15
15
|
jobs:
|
|
16
16
|
actions:
|
|
17
|
-
confirm_destroy:
|
|
18
|
-
confirm_discard:
|
|
19
|
-
confirm_reschedule:
|
|
20
|
-
confirm_retry:
|
|
21
|
-
destroy:
|
|
22
|
-
discard:
|
|
23
|
-
reschedule:
|
|
17
|
+
confirm_destroy: Bist du sicher, dass du diesen Job zerstören wilst?
|
|
18
|
+
confirm_discard: Bist du sicher, dass du diesen Job verwerfen willst?
|
|
19
|
+
confirm_reschedule: Bist du sicher, dass du diesen Job verschieben willst?
|
|
20
|
+
confirm_retry: Bist du sicher, dass du diesen Job erneut versuchen willst?
|
|
21
|
+
destroy: Job zerstören
|
|
22
|
+
discard: Job verwerfen
|
|
23
|
+
reschedule: Job neu planen
|
|
24
24
|
retry: Job wiederholen
|
|
25
25
|
title: Aktionen
|
|
26
26
|
no_jobs_found: Keine Jobs gefunden.
|
|
@@ -29,12 +29,12 @@ de:
|
|
|
29
29
|
batched_jobs: Batch-Jobs
|
|
30
30
|
callback_jobs: Callback-Jobs
|
|
31
31
|
table:
|
|
32
|
-
no_batches_found: Keine
|
|
32
|
+
no_batches_found: Keine Batches gefunden.
|
|
33
33
|
cron_entries:
|
|
34
34
|
actions:
|
|
35
|
-
confirm_disable:
|
|
36
|
-
confirm_enable:
|
|
37
|
-
confirm_enqueue:
|
|
35
|
+
confirm_disable: Bist du sicher, dass du diesen Cron-Eintrag deaktivieren willst?
|
|
36
|
+
confirm_enable: Bist du sicher, dass du diesen Cron-Eintrag aktivieren willst?
|
|
37
|
+
confirm_enqueue: Bist du sicher, dass du diesen Cron-Eintrag in die Warteschlange stellen willst?
|
|
38
38
|
disable: Cron-Eintrag deaktivieren
|
|
39
39
|
enable: Cron-Eintrag aktivieren
|
|
40
40
|
enqueue: Cron-Eintrag jetzt einreihen
|
|
@@ -48,7 +48,7 @@ de:
|
|
|
48
48
|
no_cron_schedules_found: Keine Cron-Zeitpläne gefunden.
|
|
49
49
|
title: Cron-Zeitpläne
|
|
50
50
|
show:
|
|
51
|
-
cron_entry_key: Cron-
|
|
51
|
+
cron_entry_key: Cron-Schlüssel
|
|
52
52
|
datetime:
|
|
53
53
|
distance_in_words:
|
|
54
54
|
about_x_hours:
|
|
@@ -95,11 +95,11 @@ de:
|
|
|
95
95
|
minutes: "%{min}m %{sec}s"
|
|
96
96
|
seconds: "%{sec}s"
|
|
97
97
|
error_event:
|
|
98
|
-
discarded:
|
|
98
|
+
discarded: Verworfen
|
|
99
99
|
handled: Abgewickelt
|
|
100
100
|
interrupted: Unterbrochen
|
|
101
101
|
retried: Wiederholt
|
|
102
|
-
retry_stopped:
|
|
102
|
+
retry_stopped: Wiederholung gestoppt
|
|
103
103
|
unhandled: Unbehandelt
|
|
104
104
|
helpers:
|
|
105
105
|
relative_time:
|
|
@@ -107,20 +107,20 @@ de:
|
|
|
107
107
|
past: Vor %{time}
|
|
108
108
|
jobs:
|
|
109
109
|
actions:
|
|
110
|
-
confirm_destroy:
|
|
111
|
-
confirm_discard:
|
|
112
|
-
confirm_force_discard:
|
|
113
|
-
confirm_reschedule:
|
|
114
|
-
confirm_retry:
|
|
115
|
-
destroy:
|
|
116
|
-
discard:
|
|
110
|
+
confirm_destroy: Bist du sicher, dass du diesen Job zerstören willst?
|
|
111
|
+
confirm_discard: Bist du sicher, dass du diesen Job verwerfen willst?
|
|
112
|
+
confirm_force_discard: Bist du sicher, dass du das Verwerfen dieses Jobs erzwingen möchten? Der Job wird als verworfen markiert, aber der laufende Job wird nicht gestoppt – er wird jedoch bei Fehlern nicht erneut versucht.
|
|
113
|
+
confirm_reschedule: Bist du sicher, dass du diesen Job verschieben willst?
|
|
114
|
+
confirm_retry: Bist du sicher, dass du diesen Job wiederholen willst?
|
|
115
|
+
destroy: Job zerstören
|
|
116
|
+
discard: Job verwerfen
|
|
117
117
|
force_discard: Job verwerfen erzwingen
|
|
118
|
-
reschedule:
|
|
118
|
+
reschedule: Job neu planen
|
|
119
119
|
retry: Job wiederholen
|
|
120
120
|
destroy:
|
|
121
121
|
notice: Hiob wurde zerstört
|
|
122
122
|
discard:
|
|
123
|
-
notice:
|
|
123
|
+
notice: Job wurde verworfen
|
|
124
124
|
executions:
|
|
125
125
|
application_trace: Application Trace
|
|
126
126
|
full_trace: Full Trace
|
|
@@ -131,25 +131,25 @@ de:
|
|
|
131
131
|
notice: Der Job wurde zwangsweise verworfen. Die Ausführung wird fortgesetzt, bei Fehlern wird der Vorgang jedoch nicht wiederholt
|
|
132
132
|
index:
|
|
133
133
|
job_pagination: Job-Paginierung
|
|
134
|
-
older_jobs: Ältere
|
|
134
|
+
older_jobs: Ältere Jobs
|
|
135
135
|
reschedule:
|
|
136
|
-
notice:
|
|
136
|
+
notice: Job wurde neu planen
|
|
137
137
|
retry:
|
|
138
|
-
notice:
|
|
138
|
+
notice: Job wurde wiederholt
|
|
139
139
|
show:
|
|
140
|
-
jobs:
|
|
140
|
+
jobs: Jobs
|
|
141
141
|
table:
|
|
142
142
|
actions:
|
|
143
143
|
apply_to_all:
|
|
144
|
-
one:
|
|
145
|
-
other:
|
|
146
|
-
confirm_destroy_all:
|
|
147
|
-
confirm_discard_all:
|
|
148
|
-
confirm_reschedule_all:
|
|
149
|
-
confirm_retry_all:
|
|
150
|
-
destroy_all:
|
|
144
|
+
one: Auf einen Job anwenden.
|
|
145
|
+
other: Auf alle %{count} Jobs anwenden.
|
|
146
|
+
confirm_destroy_all: Bist du sicher, dass du die ausgewählten Jobs löschen willst?
|
|
147
|
+
confirm_discard_all: Bist du sicher, dass du die ausgewählten Jobs verferfen willst?
|
|
148
|
+
confirm_reschedule_all: Bist du sicher, dass du die ausgewählten Jobs neu planen willst?
|
|
149
|
+
confirm_retry_all: Bist du sicher, dass du die ausgewählten Jobs wiederholen willst?
|
|
150
|
+
destroy_all: Alle zerstören
|
|
151
151
|
discard_all: Alle verwerfen
|
|
152
|
-
reschedule_all:
|
|
152
|
+
reschedule_all: Alle neu planen
|
|
153
153
|
retry_all: Alle wiederholen
|
|
154
154
|
title: Aktionen
|
|
155
155
|
no_jobs_found: Keine Jobs gefunden.
|
|
@@ -165,7 +165,7 @@ de:
|
|
|
165
165
|
enqueued_at: Eingereiht bei
|
|
166
166
|
finished: Fertig
|
|
167
167
|
finished_at: Fertig um
|
|
168
|
-
jobs:
|
|
168
|
+
jobs: Jobs
|
|
169
169
|
name: Name
|
|
170
170
|
cron:
|
|
171
171
|
class: Klasse
|
|
@@ -197,9 +197,9 @@ de:
|
|
|
197
197
|
performance:
|
|
198
198
|
index:
|
|
199
199
|
average_duration: Durchschnittliche Dauer
|
|
200
|
-
chart_title: Gesamtdauer der
|
|
201
|
-
executions:
|
|
202
|
-
job_class:
|
|
200
|
+
chart_title: Gesamtdauer der Jobs in Sekunden
|
|
201
|
+
executions: Ausführungen
|
|
202
|
+
job_class: Job-Klasse
|
|
203
203
|
maximum_duration: Maximale Dauer
|
|
204
204
|
minimum_duration: Mindestdauer
|
|
205
205
|
title: Leistung
|
|
@@ -210,8 +210,8 @@ de:
|
|
|
210
210
|
index:
|
|
211
211
|
cron_enabled: Cron aktiviert
|
|
212
212
|
no_good_job_processes_found: Keine GoodJob-Prozesse gefunden.
|
|
213
|
-
process:
|
|
214
|
-
schedulers:
|
|
213
|
+
process: Prozess
|
|
214
|
+
schedulers: Scheduler
|
|
215
215
|
started: Gestartet
|
|
216
216
|
title: Prozesse
|
|
217
217
|
updated: Aktualisiert
|
|
@@ -224,9 +224,9 @@ de:
|
|
|
224
224
|
all: Alle
|
|
225
225
|
all_jobs: Alle Jobs
|
|
226
226
|
all_queues: Alle Warteschlangen
|
|
227
|
-
clear:
|
|
228
|
-
job_name:
|
|
229
|
-
placeholder:
|
|
227
|
+
clear: Löschen
|
|
228
|
+
job_name: Job-Name
|
|
229
|
+
placeholder: Suche nach Klasse, Job-ID, Jobparameter und Fehlertext.
|
|
230
230
|
queue_name: Warteschlangenname
|
|
231
231
|
search: Suchen
|
|
232
232
|
navbar:
|
|
@@ -240,14 +240,14 @@ de:
|
|
|
240
240
|
theme:
|
|
241
241
|
auto: Auto
|
|
242
242
|
dark: Dunkel
|
|
243
|
-
light:
|
|
244
|
-
theme:
|
|
243
|
+
light: Hell
|
|
244
|
+
theme: Theme
|
|
245
245
|
pending_migrations: GoodJob hat ausstehende Datenbankmigrationen.
|
|
246
246
|
secondary_navbar:
|
|
247
247
|
inspiration: Denk daran, auch du machst einen guten Job!
|
|
248
248
|
last_updated: Zuletzt aktualisiert
|
|
249
249
|
status:
|
|
250
|
-
discarded:
|
|
250
|
+
discarded: Verworfen
|
|
251
251
|
queued: In der Warteschlange
|
|
252
252
|
retried: Wiederholt
|
|
253
253
|
running: Laufend
|
data/config/locales/es.yml
CHANGED
|
@@ -10,8 +10,8 @@ es:
|
|
|
10
10
|
retry: Reintentar
|
|
11
11
|
batches:
|
|
12
12
|
index:
|
|
13
|
-
older_batches:
|
|
14
|
-
title:
|
|
13
|
+
older_batches: Lotes anteriores
|
|
14
|
+
title: Lotes
|
|
15
15
|
jobs:
|
|
16
16
|
actions:
|
|
17
17
|
confirm_destroy: "¿Estás seguro que querés eliminar esta tarea?"
|
|
@@ -26,10 +26,10 @@ es:
|
|
|
26
26
|
no_jobs_found: No hay tareas.
|
|
27
27
|
show:
|
|
28
28
|
attributes: Atributos
|
|
29
|
-
batched_jobs:
|
|
29
|
+
batched_jobs: Tareas en lote
|
|
30
30
|
callback_jobs: Callback Jobs
|
|
31
31
|
table:
|
|
32
|
-
no_batches_found: No hay
|
|
32
|
+
no_batches_found: No hay lotes.
|
|
33
33
|
cron_entries:
|
|
34
34
|
actions:
|
|
35
35
|
confirm_disable: "¿Estás seguro que querés deshabilitar esta tarea programada?"
|
|
@@ -109,12 +109,12 @@ es:
|
|
|
109
109
|
actions:
|
|
110
110
|
confirm_destroy: "¿Estás seguro que querés eliminar esta tarea?"
|
|
111
111
|
confirm_discard: "¿Estás seguro que querés descartar esta tarea?"
|
|
112
|
-
confirm_force_discard: "¿Está seguro de que desea forzar el descarte de
|
|
112
|
+
confirm_force_discard: "¿Está seguro de que desea forzar el descarte de esta tarea? La tarea se marcará como descartada, pero la tarea en ejecución no se detendrá; sin embargo, no se volverá a intentar en caso de falla.\n"
|
|
113
113
|
confirm_reschedule: "¿Estás seguro que querés reprogramar esta tarea?"
|
|
114
114
|
confirm_retry: "¿Estás seguro que querés reintentar esta tarea?"
|
|
115
115
|
destroy: Eliminar tarea
|
|
116
116
|
discard: Descartar tarea
|
|
117
|
-
force_discard: Forzar el descarte
|
|
117
|
+
force_discard: Forzar el descarte de la tarea
|
|
118
118
|
reschedule: Reprogramar tarea
|
|
119
119
|
retry: Reiuntentar tarea
|
|
120
120
|
destroy:
|
|
@@ -122,13 +122,13 @@ es:
|
|
|
122
122
|
discard:
|
|
123
123
|
notice: La tarea ha sido descartada
|
|
124
124
|
executions:
|
|
125
|
-
application_trace:
|
|
126
|
-
full_trace:
|
|
125
|
+
application_trace: Traza de la aplicación
|
|
126
|
+
full_trace: Traza completa
|
|
127
127
|
in_queue: en cola
|
|
128
128
|
runtime: en ejecución
|
|
129
129
|
title: Ejecuciones
|
|
130
130
|
force_discard:
|
|
131
|
-
notice:
|
|
131
|
+
notice: La tarea ha sido descartada a la fuerza. Continuará ejecutándose pero no se volverá a intentar en caso de fallas.
|
|
132
132
|
index:
|
|
133
133
|
job_pagination: Paginación de tareas
|
|
134
134
|
older_jobs: Tareas anteriores
|
|
@@ -197,12 +197,12 @@ es:
|
|
|
197
197
|
performance:
|
|
198
198
|
index:
|
|
199
199
|
average_duration: Duración promedio
|
|
200
|
-
chart_title: Tiempo total de ejecución
|
|
200
|
+
chart_title: Tiempo total de ejecución de la tarea en segundos
|
|
201
201
|
executions: Ejecuciones
|
|
202
|
-
job_class: clase de
|
|
202
|
+
job_class: clase de tarea
|
|
203
203
|
maximum_duration: Duración máxima
|
|
204
204
|
minimum_duration: Duración mínima
|
|
205
|
-
title:
|
|
205
|
+
title: Rendimiento
|
|
206
206
|
show:
|
|
207
207
|
slow: Lento
|
|
208
208
|
title: Actuación
|
|
@@ -230,12 +230,12 @@ es:
|
|
|
230
230
|
queue_name: Nombre de la cola
|
|
231
231
|
search: Buscar
|
|
232
232
|
navbar:
|
|
233
|
-
batches:
|
|
233
|
+
batches: Lotes
|
|
234
234
|
cron_schedules: Cron
|
|
235
235
|
jobs: Tareas
|
|
236
236
|
live_poll: En vivo
|
|
237
237
|
name: "GoodJob 👍"
|
|
238
|
-
performance:
|
|
238
|
+
performance: Rendimiento
|
|
239
239
|
processes: Procesos
|
|
240
240
|
theme:
|
|
241
241
|
auto: Auto
|
data/config/routes.rb
CHANGED
|
@@ -33,8 +33,8 @@ GoodJob::Engine.routes.draw do
|
|
|
33
33
|
|
|
34
34
|
resources :performance, only: %i[index show]
|
|
35
35
|
|
|
36
|
-
scope :frontend, controller: :frontends do
|
|
37
|
-
get "modules/:
|
|
38
|
-
get "static/:
|
|
36
|
+
scope :frontend, controller: :frontends, defaults: { version: GoodJob::VERSION.tr(".", "-") } do
|
|
37
|
+
get "modules/:version/:id", action: :module, as: :frontend_module, constraints: { format: 'js' }
|
|
38
|
+
get "static/:version/:id", action: :static, as: :frontend_static, constraints: { format: %w[css js svg] }
|
|
39
39
|
end
|
|
40
40
|
end
|
|
@@ -28,28 +28,88 @@ module GoodJob
|
|
|
28
28
|
class_attribute :good_job_concurrency_config, instance_accessor: false, default: {}
|
|
29
29
|
attr_writer :good_job_concurrency_key
|
|
30
30
|
|
|
31
|
-
if ActiveJob.gem_version >= Gem::Version.new("6.1.0")
|
|
32
|
-
before_enqueue do |job|
|
|
33
|
-
good_job_enqueue_concurrency_check(job, on_abort: -> { throw(:abort) }, on_enqueue: nil)
|
|
34
|
-
end
|
|
35
|
-
else
|
|
36
|
-
around_enqueue do |job, block|
|
|
37
|
-
good_job_enqueue_concurrency_check(job, on_abort: nil, on_enqueue: block)
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
|
|
41
31
|
wait_key = if ActiveJob.gem_version >= Gem::Version.new("7.1.0.a")
|
|
42
32
|
:polynomially_longer
|
|
43
33
|
else
|
|
44
34
|
:exponentially_longer
|
|
45
35
|
end
|
|
46
|
-
|
|
47
36
|
retry_on(
|
|
48
37
|
GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError,
|
|
49
38
|
attempts: Float::INFINITY,
|
|
50
39
|
wait: wait_key
|
|
51
40
|
)
|
|
52
41
|
|
|
42
|
+
before_enqueue do |job|
|
|
43
|
+
# Don't attempt to enforce concurrency limits with other queue adapters.
|
|
44
|
+
next unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
|
|
45
|
+
|
|
46
|
+
# Always allow jobs to be retried because the current job's execution will complete momentarily
|
|
47
|
+
next if CurrentThread.active_job_id == job.job_id
|
|
48
|
+
|
|
49
|
+
# Only generate the concurrency key on the initial enqueue in case it is dynamic
|
|
50
|
+
job.good_job_concurrency_key ||= job._good_job_concurrency_key
|
|
51
|
+
key = job.good_job_concurrency_key
|
|
52
|
+
next if key.blank?
|
|
53
|
+
|
|
54
|
+
enqueue_limit = job.class.good_job_concurrency_config[:enqueue_limit]
|
|
55
|
+
enqueue_limit = instance_exec(&enqueue_limit) if enqueue_limit.respond_to?(:call)
|
|
56
|
+
enqueue_limit = nil unless enqueue_limit.present? && (0...Float::INFINITY).cover?(enqueue_limit)
|
|
57
|
+
|
|
58
|
+
unless enqueue_limit
|
|
59
|
+
total_limit = job.class.good_job_concurrency_config[:total_limit]
|
|
60
|
+
total_limit = instance_exec(&total_limit) if total_limit.respond_to?(:call)
|
|
61
|
+
total_limit = nil unless total_limit.present? && (0...Float::INFINITY).cover?(total_limit)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
enqueue_throttle = job.class.good_job_concurrency_config[:enqueue_throttle]
|
|
65
|
+
enqueue_throttle = instance_exec(&enqueue_throttle) if enqueue_throttle.respond_to?(:call)
|
|
66
|
+
enqueue_throttle = nil unless enqueue_throttle.present? && enqueue_throttle.is_a?(Array) && enqueue_throttle.size == 2
|
|
67
|
+
|
|
68
|
+
limit = enqueue_limit || total_limit
|
|
69
|
+
throttle = enqueue_throttle
|
|
70
|
+
next unless limit || throttle
|
|
71
|
+
|
|
72
|
+
exceeded = nil
|
|
73
|
+
GoodJob::Job.transaction(requires_new: true, joinable: false) do
|
|
74
|
+
GoodJob::Job.advisory_lock_key(key, function: "pg_advisory_xact_lock") do
|
|
75
|
+
if limit
|
|
76
|
+
enqueue_concurrency = if enqueue_limit
|
|
77
|
+
GoodJob::Job.where(concurrency_key: key).unfinished.advisory_unlocked.count
|
|
78
|
+
else
|
|
79
|
+
GoodJob::Job.where(concurrency_key: key).unfinished.count
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# The job has not yet been enqueued, so check if adding it will go over the limit
|
|
83
|
+
if (enqueue_concurrency + 1) > limit
|
|
84
|
+
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)}"
|
|
85
|
+
exceeded = :limit
|
|
86
|
+
next
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if throttle
|
|
91
|
+
throttle_limit = throttle[0]
|
|
92
|
+
throttle_period = throttle[1]
|
|
93
|
+
enqueued_within_period = GoodJob::Job.where(concurrency_key: key)
|
|
94
|
+
.where(GoodJob::Job.arel_table[:created_at].gt(throttle_period.ago))
|
|
95
|
+
.count
|
|
96
|
+
|
|
97
|
+
if (enqueued_within_period + 1) > throttle_limit
|
|
98
|
+
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)}"
|
|
99
|
+
exceeded = :throttle
|
|
100
|
+
next
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Rollback the transaction because it's potentially less expensive than committing it
|
|
106
|
+
# even though nothing has been altered in the transaction.
|
|
107
|
+
raise ActiveRecord::Rollback
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
throw :abort if exceeded
|
|
111
|
+
end
|
|
112
|
+
|
|
53
113
|
before_perform do |job|
|
|
54
114
|
# Don't attempt to enforce concurrency limits with other queue adapters.
|
|
55
115
|
next unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
|
|
@@ -80,30 +140,43 @@ module GoodJob
|
|
|
80
140
|
next
|
|
81
141
|
end
|
|
82
142
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
143
|
+
exceeded = nil
|
|
144
|
+
GoodJob::Job.transaction(requires_new: true, joinable: false) do
|
|
145
|
+
GoodJob::Job.advisory_lock_key(key, function: "pg_advisory_xact_lock") do
|
|
146
|
+
if limit
|
|
147
|
+
allowed_active_job_ids = GoodJob::Job.unfinished.where(concurrency_key: key)
|
|
148
|
+
.advisory_locked
|
|
149
|
+
.order(Arel.sql("COALESCE(performed_at, scheduled_at, created_at) ASC"))
|
|
150
|
+
.limit(limit).pluck(:active_job_id)
|
|
151
|
+
# The current job has already been locked and will appear in the previous query
|
|
152
|
+
exceeded = :limit unless allowed_active_job_ids.include?(job.job_id)
|
|
153
|
+
next
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if throttle
|
|
157
|
+
throttle_limit = throttle[0]
|
|
158
|
+
throttle_period = throttle[1]
|
|
159
|
+
|
|
160
|
+
query = Execution.joins(:job)
|
|
161
|
+
.where(GoodJob::Job.table_name => { concurrency_key: key })
|
|
162
|
+
.where(Execution.arel_table[:created_at].gt(Execution.bind_value('created_at', throttle_period.ago, ActiveRecord::Type::DateTime)))
|
|
163
|
+
allowed_active_job_ids = query.where(error: nil).or(query.where.not(error: "GoodJob::ActiveJobExtensions::Concurrency::ThrottleExceededError: GoodJob::ActiveJobExtensions::Concurrency::ThrottleExceededError"))
|
|
164
|
+
.order(created_at: :asc)
|
|
165
|
+
.limit(throttle_limit)
|
|
166
|
+
.pluck(:active_job_id)
|
|
167
|
+
|
|
168
|
+
exceeded = :throttle unless allowed_active_job_ids.include?(job.job_id)
|
|
169
|
+
next
|
|
170
|
+
end
|
|
91
171
|
end
|
|
92
172
|
|
|
93
|
-
|
|
94
|
-
|
|
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(DiscreteExecution.bind_value('created_at', throttle_period.ago, ActiveRecord::Type::DateTime)))
|
|
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)
|
|
173
|
+
raise ActiveRecord::Rollback
|
|
174
|
+
end
|
|
104
175
|
|
|
105
|
-
|
|
106
|
-
|
|
176
|
+
if exceeded == :limit
|
|
177
|
+
raise GoodJob::ActiveJobExtensions::Concurrency::ConcurrencyExceededError
|
|
178
|
+
elsif exceeded == :throttle
|
|
179
|
+
raise GoodJob::ActiveJobExtensions::Concurrency::ThrottleExceededError
|
|
107
180
|
end
|
|
108
181
|
end
|
|
109
182
|
end
|
|
@@ -139,72 +212,6 @@ module GoodJob
|
|
|
139
212
|
def _good_job_default_concurrency_key
|
|
140
213
|
self.class.name.to_s
|
|
141
214
|
end
|
|
142
|
-
|
|
143
|
-
private
|
|
144
|
-
|
|
145
|
-
def good_job_enqueue_concurrency_check(job, on_abort:, on_enqueue:)
|
|
146
|
-
# Don't attempt to enforce concurrency limits with other queue adapters.
|
|
147
|
-
return on_enqueue&.call unless job.class.queue_adapter.is_a?(GoodJob::Adapter)
|
|
148
|
-
|
|
149
|
-
# Always allow jobs to be retried because the current job's execution will complete momentarily
|
|
150
|
-
return on_enqueue&.call if CurrentThread.active_job_id == job.job_id
|
|
151
|
-
|
|
152
|
-
# Only generate the concurrency key on the initial enqueue in case it is dynamic
|
|
153
|
-
job.good_job_concurrency_key ||= job._good_job_concurrency_key
|
|
154
|
-
key = job.good_job_concurrency_key
|
|
155
|
-
return on_enqueue&.call if key.blank?
|
|
156
|
-
|
|
157
|
-
enqueue_limit = job.class.good_job_concurrency_config[:enqueue_limit]
|
|
158
|
-
enqueue_limit = instance_exec(&enqueue_limit) if enqueue_limit.respond_to?(:call)
|
|
159
|
-
enqueue_limit = nil unless enqueue_limit.present? && (0...Float::INFINITY).cover?(enqueue_limit)
|
|
160
|
-
|
|
161
|
-
unless enqueue_limit
|
|
162
|
-
total_limit = job.class.good_job_concurrency_config[:total_limit]
|
|
163
|
-
total_limit = instance_exec(&total_limit) if total_limit.respond_to?(:call)
|
|
164
|
-
total_limit = nil unless total_limit.present? && (0...Float::INFINITY).cover?(total_limit)
|
|
165
|
-
end
|
|
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
|
-
|
|
171
|
-
limit = enqueue_limit || total_limit
|
|
172
|
-
throttle = enqueue_throttle
|
|
173
|
-
return on_enqueue&.call unless limit || throttle
|
|
174
|
-
|
|
175
|
-
GoodJob::Job.advisory_lock_key(key, function: "pg_advisory_lock") do
|
|
176
|
-
if limit
|
|
177
|
-
enqueue_concurrency = if enqueue_limit
|
|
178
|
-
GoodJob::Job.where(concurrency_key: key).unfinished.advisory_unlocked.count
|
|
179
|
-
else
|
|
180
|
-
GoodJob::Job.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
|
|
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
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
215
|
end
|
|
209
216
|
end
|
|
210
217
|
end
|