good_job 4.1.0 → 4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|