good_job 3.19.4 → 3.21.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +48 -0
- data/README.md +2 -2
- data/app/models/concerns/good_job/advisory_lockable.rb +8 -6
- data/app/models/good_job/batch.rb +7 -3
- data/app/models/good_job/execution.rb +5 -2
- data/app/views/good_job/processes/index.html.erb +2 -0
- data/config/locales/de.yml +4 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/es.yml +4 -0
- data/config/locales/fr.yml +4 -0
- data/config/locales/ja.yml +4 -0
- data/config/locales/nl.yml +4 -0
- data/config/locales/ru.yml +4 -0
- data/config/locales/tr.yml +4 -0
- data/config/locales/uk.yml +4 -0
- data/lib/good_job/adapter.rb +82 -78
- data/lib/good_job/capsule.rb +5 -5
- data/lib/good_job/cli.rb +8 -4
- data/lib/good_job/version.rb +1 -1
- data/lib/good_job.rb +12 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1e8a8a3ec28224d48f23c9c2795cd15f9eb515ba9059ab2bea4316a03b42aa9
|
4
|
+
data.tar.gz: e469a0f1be765789433ef9d0096c6c1c3fe3e87c90a184c11996a204a36dabd1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6546caba43d15714d87c2b6f3a072b493804685411f7fdd3c73b8aaa0aacffb2f77ae8fcbb8f3eb0d9aeed9fbfab2c4175b49adebd3d5356a8994138b92e3852
|
7
|
+
data.tar.gz: 30f85524258b9f1b2b774601ef0fd23965ed21eac87b0a3c0491a94c0fe85a3c70efb57b9979d11047d8ea5176cf0008317c860c27411db86ecdf8ef2fd0a69c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,53 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [v3.21.0](https://github.com/bensheldon/good_job/tree/v3.21.0) (2023-11-06)
|
4
|
+
|
5
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.20.0...v3.21.0)
|
6
|
+
|
7
|
+
**Implemented enhancements:**
|
8
|
+
|
9
|
+
- Add "cron enabled" column to processes index page [\#1127](https://github.com/bensheldon/good_job/pull/1127) ([bforma](https://github.com/bforma))
|
10
|
+
- Add `limit:` kwarg to `GoodJob.perform_inline` [\#1126](https://github.com/bensheldon/good_job/pull/1126) ([bensheldon](https://github.com/bensheldon))
|
11
|
+
|
12
|
+
**Closed issues:**
|
13
|
+
|
14
|
+
- Cron scheduler and multiple processes [\#1128](https://github.com/bensheldon/good_job/issues/1128)
|
15
|
+
- `GoodJob.on_thread_error` not called in tests [\#1102](https://github.com/bensheldon/good_job/issues/1102)
|
16
|
+
|
17
|
+
**Merged pull requests:**
|
18
|
+
|
19
|
+
- Use a Concurrent::Event for CLI signal-trapping loop [\#1141](https://github.com/bensheldon/good_job/pull/1141) ([bensheldon](https://github.com/bensheldon))
|
20
|
+
- Update README's optimize queue explanation [\#1138](https://github.com/bensheldon/good_job/pull/1138) ([maestromac](https://github.com/maestromac))
|
21
|
+
- Update development dependencies and light Rubocop'ing [\#1136](https://github.com/bensheldon/good_job/pull/1136) ([bensheldon](https://github.com/bensheldon))
|
22
|
+
- Move the Rails app harness from `spec/test_app` to `demo` [\#1135](https://github.com/bensheldon/good_job/pull/1135) ([bensheldon](https://github.com/bensheldon))
|
23
|
+
- In test, shutdown schedulers/capsules before doing assertions because of race conditions; store CI logs for Dev Env tests [\#1129](https://github.com/bensheldon/good_job/pull/1129) ([bensheldon](https://github.com/bensheldon))
|
24
|
+
- Use a constant to represent `None` for default/blank memoizable values [\#1125](https://github.com/bensheldon/good_job/pull/1125) ([bensheldon](https://github.com/bensheldon))
|
25
|
+
|
26
|
+
## [v3.20.0](https://github.com/bensheldon/good_job/tree/v3.20.0) (2023-10-23)
|
27
|
+
|
28
|
+
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.19.4...v3.20.0)
|
29
|
+
|
30
|
+
**Closed issues:**
|
31
|
+
|
32
|
+
- Dashboard blocked in iframe tag [\#1111](https://github.com/bensheldon/good_job/issues/1111)
|
33
|
+
- PG::ConnectionBad: PQsocket\(\) can't get socket descriptor [\#1100](https://github.com/bensheldon/good_job/issues/1100)
|
34
|
+
|
35
|
+
**Merged pull requests:**
|
36
|
+
|
37
|
+
- Wrap Adapter enqueue methods and Batch callbacks with Rails Reloader; verify in tests that no Advisory locks remain at database connection check-in [\#1124](https://github.com/bensheldon/good_job/pull/1124) ([bensheldon](https://github.com/bensheldon))
|
38
|
+
- Run all RSpec examples within a Rails Executor [\#1122](https://github.com/bensheldon/good_job/pull/1122) ([bensheldon](https://github.com/bensheldon))
|
39
|
+
- Print better debugging for retained advisory locks in test [\#1121](https://github.com/bensheldon/good_job/pull/1121) ([bensheldon](https://github.com/bensheldon))
|
40
|
+
- Replace Heroku-specific Rake tasks with `db:prepare` now that Demo is upgraded to Rails 7.1 [\#1120](https://github.com/bensheldon/good_job/pull/1120) ([bensheldon](https://github.com/bensheldon))
|
41
|
+
- Do not error debug logs if `pg_stat_activity` join is empty [\#1119](https://github.com/bensheldon/good_job/pull/1119) ([bensheldon](https://github.com/bensheldon))
|
42
|
+
- Remove pinned psych version [\#1114](https://github.com/bensheldon/good_job/pull/1114) ([bensheldon](https://github.com/bensheldon))
|
43
|
+
- Remove `pg_advisory_unlock_all()` after job is run; only verify blank `finished_at` \(and not lock presence\) before performing job [\#1113](https://github.com/bensheldon/good_job/pull/1113) ([bensheldon](https://github.com/bensheldon))
|
44
|
+
- Update docs url [\#1112](https://github.com/bensheldon/good_job/pull/1112) ([ur5us](https://github.com/ur5us))
|
45
|
+
- Pin psych gem 5.1.0 [\#1108](https://github.com/bensheldon/good_job/pull/1108) ([bensheldon](https://github.com/bensheldon))
|
46
|
+
- Add sampling for Skylight traces on Demo [\#1107](https://github.com/bensheldon/good_job/pull/1107) ([bensheldon](https://github.com/bensheldon))
|
47
|
+
- Add Rails 7.1 to test matrix [\#1105](https://github.com/bensheldon/good_job/pull/1105) ([bensheldon](https://github.com/bensheldon))
|
48
|
+
- Add spec to verify unhandled thread errors are reported [\#1104](https://github.com/bensheldon/good_job/pull/1104) ([bensheldon](https://github.com/bensheldon))
|
49
|
+
- Update Codespace configuration [\#1101](https://github.com/bensheldon/good_job/pull/1101) ([bensheldon](https://github.com/bensheldon))
|
50
|
+
|
3
51
|
## [v3.19.4](https://github.com/bensheldon/good_job/tree/v3.19.4) (2023-10-04)
|
4
52
|
|
5
53
|
[Full Changelog](https://github.com/bensheldon/good_job/compare/v3.19.3...v3.19.4)
|
data/README.md
CHANGED
@@ -939,7 +939,7 @@ By default, GoodJob creates a single thread execution pool that will execute job
|
|
939
939
|
|
940
940
|
- `transactional_messages:2`: execute jobs enqueued on `transactional_messages`, with up to 2 threads.
|
941
941
|
- `batch_processing:1` execute jobs enqueued on `batch_processing`, with a single thread.
|
942
|
-
- `-transactional_messages,batch_processing`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing`, with up to 2 threads.
|
942
|
+
- `-transactional_messages,batch_processing:2`: execute jobs enqueued on _any_ queue _excluding_ `transactional_messages` or `batch_processing`, with up to 2 threads.
|
943
943
|
- `*`: execute jobs on any queue, with up to 5 threads (as configured by `--max-threads=5`).
|
944
944
|
|
945
945
|
When a pool is performing jobs from multiple queues, jobs will be performed from specified queues, ordered by priority and creation time. To perform jobs from queues in the queues' given order, use the `+` modifier. In this example, jobs in `batch_processing` will be performed only when there are no jobs in `transactional_messages`:
|
@@ -1370,7 +1370,7 @@ bin/setup
|
|
1370
1370
|
|
1371
1371
|
#### Rails development harness
|
1372
1372
|
|
1373
|
-
A Rails application exists within `
|
1373
|
+
A Rails application exists within `demo` that is used for development, test, and GoodJob Demo environments.
|
1374
1374
|
|
1375
1375
|
```bash
|
1376
1376
|
# Run a local development webserver
|
@@ -53,7 +53,7 @@ module GoodJob
|
|
53
53
|
composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::SqlLiteral.new([cte_type, "(", cte_query.to_sql, ")"].join(' ')))
|
54
54
|
query = cte_table.project(cte_table[:id])
|
55
55
|
.with(composed_cte)
|
56
|
-
.where(Arel.sql(
|
56
|
+
.where(Arel.sql("#{function}(('x' || substr(md5(#{connection.quote(table_name)} || '-' || #{connection.quote_table_name(cte_table.name)}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64)::bigint)"))
|
57
57
|
|
58
58
|
limit = original_query.arel.ast.limit
|
59
59
|
query.limit = limit.value if limit.present?
|
@@ -74,14 +74,12 @@ module GoodJob
|
|
74
74
|
# @example Get the records that have a session awaiting a lock:
|
75
75
|
# MyLockableRecord.joins_advisory_locks.where("pg_locks.granted = ?", false)
|
76
76
|
scope :joins_advisory_locks, (lambda do |column: _advisory_lockable_column|
|
77
|
-
|
77
|
+
joins(<<~SQL.squish)
|
78
78
|
LEFT JOIN pg_locks ON pg_locks.locktype = 'advisory'
|
79
79
|
AND pg_locks.objsubid = 1
|
80
|
-
AND pg_locks.classid = ('x' || substr(md5(
|
81
|
-
AND pg_locks.objid = (('x' || substr(md5(
|
80
|
+
AND pg_locks.classid = ('x' || substr(md5(#{connection.quote(table_name)} || '-' || #{quoted_table_name}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(32)::int
|
81
|
+
AND pg_locks.objid = (('x' || substr(md5(#{connection.quote(table_name)} || '-' || #{quoted_table_name}.#{connection.quote_column_name(column)}::text), 1, 16))::bit(64) << 32)::bit(32)::int
|
82
82
|
SQL
|
83
|
-
|
84
|
-
joins(sanitize_sql_for_conditions([join_sql, { table_name: table_name }]))
|
85
83
|
end)
|
86
84
|
|
87
85
|
# Joins the current query with Postgres's +pg_locks+ table AND SELECTs the resulting columns
|
@@ -151,6 +149,10 @@ module GoodJob
|
|
151
149
|
# can (as in {Lockable.advisory_lock}) and only pass those that could be
|
152
150
|
# locked to the block.
|
153
151
|
#
|
152
|
+
# If the Active Record Relation has WHERE conditions that have the potential
|
153
|
+
# to be updated/changed elsewhere, be sure to verify the conditions are still
|
154
|
+
# satisfied, or check the lock status, as an unlocked and out-of-date record could be returned.
|
155
|
+
#
|
154
156
|
# @param column [String, Symbol] name of advisory lock or unlock function
|
155
157
|
# @param function [String, Symbol] Postgres Advisory Lock function name to use
|
156
158
|
# @param unlock_session [Boolean] Whether to unlock all advisory locks in the session afterwards
|
@@ -101,9 +101,13 @@ module GoodJob
|
|
101
101
|
|
102
102
|
active_jobs = add(active_jobs, &block)
|
103
103
|
|
104
|
-
|
105
|
-
record.
|
106
|
-
|
104
|
+
Rails.application.reloader.wrap do
|
105
|
+
record.with_advisory_lock(function: "pg_advisory_lock") do
|
106
|
+
record.update!(enqueued_at: Time.current)
|
107
|
+
|
108
|
+
# During inline execution, this could enqueue and execute further jobs
|
109
|
+
record._continue_discard_or_finish(lock: false)
|
110
|
+
end
|
107
111
|
end
|
108
112
|
|
109
113
|
active_jobs
|
@@ -256,12 +256,13 @@ module GoodJob
|
|
256
256
|
def self.perform_with_advisory_lock(parsed_queues: nil, queue_select_limit: nil)
|
257
257
|
execution = nil
|
258
258
|
result = nil
|
259
|
-
unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(
|
259
|
+
unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(select_limit: queue_select_limit) do |executions|
|
260
260
|
execution = executions.first
|
261
261
|
break if execution.blank?
|
262
262
|
|
263
263
|
unless execution.executable?
|
264
264
|
result = ExecutionResult.new(value: nil, unexecutable: true)
|
265
|
+
execution = nil
|
265
266
|
break
|
266
267
|
end
|
267
268
|
|
@@ -491,7 +492,9 @@ module GoodJob
|
|
491
492
|
# Tests whether this job is safe to be executed by this thread.
|
492
493
|
# @return [Boolean]
|
493
494
|
def executable?
|
494
|
-
|
495
|
+
reload.finished_at.blank?
|
496
|
+
rescue ActiveRecord::RecordNotFound
|
497
|
+
false
|
495
498
|
end
|
496
499
|
|
497
500
|
def make_discrete
|
@@ -8,6 +8,7 @@
|
|
8
8
|
<div class="row small text-muted text-uppercase align-items-center">
|
9
9
|
<div class="col"><%= t ".process" %></div>
|
10
10
|
<div class="col"><%= t ".schedulers" %></div>
|
11
|
+
<div class="col-2 d-flex gap-2"><%= t ".cron_enabled" %></div>
|
11
12
|
<div class="col-2 d-flex gap-2">
|
12
13
|
<span><%= t ".started" %></span>
|
13
14
|
</div>
|
@@ -44,6 +45,7 @@
|
|
44
45
|
<pre class="mb-0"><%= scheduler.is_a?(Hash) ? scheduler['name'] : scheduler %></pre>
|
45
46
|
<% end %>
|
46
47
|
</div>
|
48
|
+
<div class="col-2 small"><%= t(ActiveModel::Type::Boolean.new.cast(process.state["cron_enabled"]), scope: "good_job.shared.boolean") %></div>
|
47
49
|
<div class="col-2 small"><%= relative_time(process.created_at) %></div>
|
48
50
|
<div class="col-2 small"><%= relative_time(process.updated_at) %></div>
|
49
51
|
<div class="col-auto">
|
data/config/locales/de.yml
CHANGED
@@ -198,6 +198,7 @@ de:
|
|
198
198
|
unit: ''
|
199
199
|
processes:
|
200
200
|
index:
|
201
|
+
cron_enabled: Cron aktiviert
|
201
202
|
no_good_job_processes_found: Keine GoodJob-Prozesse gefunden.
|
202
203
|
process: Verfahren
|
203
204
|
schedulers: Planer
|
@@ -205,6 +206,9 @@ de:
|
|
205
206
|
title: Prozesse
|
206
207
|
updated: Aktualisiert
|
207
208
|
shared:
|
209
|
+
boolean:
|
210
|
+
'false': Nein
|
211
|
+
'true': Ja
|
208
212
|
error: Fehler
|
209
213
|
filter:
|
210
214
|
all: Alle
|
data/config/locales/en.yml
CHANGED
@@ -198,6 +198,7 @@ en:
|
|
198
198
|
unit: ''
|
199
199
|
processes:
|
200
200
|
index:
|
201
|
+
cron_enabled: Cron enabled
|
201
202
|
no_good_job_processes_found: No GoodJob processes found.
|
202
203
|
process: Process
|
203
204
|
schedulers: Schedulers
|
@@ -205,6 +206,9 @@ en:
|
|
205
206
|
title: Processes
|
206
207
|
updated: Updated
|
207
208
|
shared:
|
209
|
+
boolean:
|
210
|
+
'false': 'No'
|
211
|
+
'true': 'Yes'
|
208
212
|
error: Error
|
209
213
|
filter:
|
210
214
|
all: All
|
data/config/locales/es.yml
CHANGED
@@ -196,6 +196,7 @@ es:
|
|
196
196
|
unit: ''
|
197
197
|
processes:
|
198
198
|
index:
|
199
|
+
cron_enabled: Cron habilitado
|
199
200
|
no_good_job_processes_found: No hay procesos de GoodJob.
|
200
201
|
process: Proceso
|
201
202
|
schedulers: Schedulers
|
@@ -203,6 +204,9 @@ es:
|
|
203
204
|
title: Procesos
|
204
205
|
updated: Actualizado
|
205
206
|
shared:
|
207
|
+
boolean:
|
208
|
+
'false': 'No'
|
209
|
+
'true': Sí
|
206
210
|
error: Error
|
207
211
|
filter:
|
208
212
|
all: Todas
|
data/config/locales/fr.yml
CHANGED
@@ -198,6 +198,7 @@ fr:
|
|
198
198
|
unit: ''
|
199
199
|
processes:
|
200
200
|
index:
|
201
|
+
cron_enabled: Cron activé
|
201
202
|
no_good_job_processes_found: Aucun processus GoodJob trouvé.
|
202
203
|
process: Processus
|
203
204
|
schedulers: Schedulers
|
@@ -205,6 +206,9 @@ fr:
|
|
205
206
|
title: Processus
|
206
207
|
updated: Mis à jour
|
207
208
|
shared:
|
209
|
+
boolean:
|
210
|
+
'false': Non
|
211
|
+
'true': Oui
|
208
212
|
error: Erreur
|
209
213
|
filter:
|
210
214
|
all: Tous
|
data/config/locales/ja.yml
CHANGED
@@ -198,6 +198,7 @@ ja:
|
|
198
198
|
unit: ''
|
199
199
|
processes:
|
200
200
|
index:
|
201
|
+
cron_enabled: Cron が有効になっている
|
201
202
|
no_good_job_processes_found: GoodJobのプロセスが見つかりませんでした。
|
202
203
|
process: プロセス
|
203
204
|
schedulers: スケジューラー
|
@@ -205,6 +206,9 @@ ja:
|
|
205
206
|
title: プロセス
|
206
207
|
updated: 更新された
|
207
208
|
shared:
|
209
|
+
boolean:
|
210
|
+
'false': いいえ
|
211
|
+
'true': はい
|
208
212
|
error: エラー
|
209
213
|
filter:
|
210
214
|
all: 全て
|
data/config/locales/nl.yml
CHANGED
@@ -198,6 +198,7 @@ nl:
|
|
198
198
|
unit: ''
|
199
199
|
processes:
|
200
200
|
index:
|
201
|
+
cron_enabled: Cron ingeschakeld
|
201
202
|
no_good_job_processes_found: Geen GoodJob-processen gevonden.
|
202
203
|
process: Proces
|
203
204
|
schedulers: Planners
|
@@ -205,6 +206,9 @@ nl:
|
|
205
206
|
title: Processen
|
206
207
|
updated: Bijgewerkt
|
207
208
|
shared:
|
209
|
+
boolean:
|
210
|
+
'false': Nee
|
211
|
+
'true': Ja
|
208
212
|
error: Fout
|
209
213
|
filter:
|
210
214
|
all: Alle
|
data/config/locales/ru.yml
CHANGED
@@ -224,6 +224,7 @@ ru:
|
|
224
224
|
unit: ''
|
225
225
|
processes:
|
226
226
|
index:
|
227
|
+
cron_enabled: Крон включен
|
227
228
|
no_good_job_processes_found: Процессы GoodJob не найдены.
|
228
229
|
process: Процесс
|
229
230
|
schedulers: Планировщики
|
@@ -231,6 +232,9 @@ ru:
|
|
231
232
|
title: Процессы
|
232
233
|
updated: Обновлено
|
233
234
|
shared:
|
235
|
+
boolean:
|
236
|
+
'false': Нет
|
237
|
+
'true': Да
|
234
238
|
error: Ошибка
|
235
239
|
filter:
|
236
240
|
all: Все
|
data/config/locales/tr.yml
CHANGED
@@ -198,6 +198,7 @@ tr:
|
|
198
198
|
unit: ''
|
199
199
|
processes:
|
200
200
|
index:
|
201
|
+
cron_enabled: Cron etkin
|
201
202
|
no_good_job_processes_found: GoodJob süreci bulunamadı.
|
202
203
|
process: Süreç
|
203
204
|
schedulers: Planlayıcılar
|
@@ -205,6 +206,9 @@ tr:
|
|
205
206
|
title: Süreçler
|
206
207
|
updated: Güncellenmiş
|
207
208
|
shared:
|
209
|
+
boolean:
|
210
|
+
'false': Hayır
|
211
|
+
'true': Evet
|
208
212
|
error: Hata
|
209
213
|
filter:
|
210
214
|
all: Tümü
|
data/config/locales/uk.yml
CHANGED
@@ -224,6 +224,7 @@ uk:
|
|
224
224
|
unit: ''
|
225
225
|
processes:
|
226
226
|
index:
|
227
|
+
cron_enabled: Cron увімкнено
|
227
228
|
no_good_job_processes_found: Процеси GoodJob не знайдені.
|
228
229
|
process: Процес
|
229
230
|
schedulers: Планувальники
|
@@ -231,6 +232,9 @@ uk:
|
|
231
232
|
title: Процеси
|
232
233
|
updated: Оновлено
|
233
234
|
shared:
|
235
|
+
boolean:
|
236
|
+
'false': Ні
|
237
|
+
'true': Так
|
234
238
|
error: Помилка
|
235
239
|
filter:
|
236
240
|
all: Всі
|
data/lib/good_job/adapter.rb
CHANGED
@@ -49,74 +49,76 @@ module GoodJob
|
|
49
49
|
active_jobs = Array(active_jobs)
|
50
50
|
return 0 if active_jobs.empty?
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
52
|
+
Rails.application.reloader.wrap do
|
53
|
+
current_time = Time.current
|
54
|
+
executions = active_jobs.map do |active_job|
|
55
|
+
GoodJob::Execution.build_for_enqueue(active_job).tap do |execution|
|
56
|
+
if GoodJob::Execution.discrete_support?
|
57
|
+
execution.make_discrete
|
58
|
+
execution.scheduled_at = current_time if execution.scheduled_at == execution.created_at
|
59
|
+
end
|
59
60
|
|
60
|
-
|
61
|
-
|
61
|
+
execution.created_at = current_time
|
62
|
+
execution.updated_at = current_time
|
63
|
+
end
|
62
64
|
end
|
63
|
-
end
|
64
65
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
66
|
+
inline_executions = []
|
67
|
+
GoodJob::Execution.transaction(requires_new: true, joinable: false) do
|
68
|
+
execution_attributes = executions.map do |execution|
|
69
|
+
if GoodJob::Execution.error_event_migrated?
|
70
|
+
execution.attributes
|
71
|
+
else
|
72
|
+
execution.attributes.except('error_event')
|
73
|
+
end
|
72
74
|
end
|
73
|
-
end
|
74
75
|
|
75
|
-
|
76
|
+
results = GoodJob::Execution.insert_all(execution_attributes, returning: %w[id active_job_id]) # rubocop:disable Rails/SkipsModelValidations
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
78
|
+
job_id_to_provider_job_id = results.each_with_object({}) { |result, hash| hash[result['active_job_id']] = result['id'] }
|
79
|
+
active_jobs.each do |active_job|
|
80
|
+
active_job.provider_job_id = job_id_to_provider_job_id[active_job.job_id]
|
81
|
+
active_job.successfully_enqueued = active_job.provider_job_id.present? if active_job.respond_to?(:successfully_enqueued=)
|
82
|
+
end
|
83
|
+
executions.each do |execution|
|
84
|
+
execution.instance_variable_set(:@new_record, false) if job_id_to_provider_job_id[execution.active_job_id]
|
85
|
+
end
|
86
|
+
executions = executions.select(&:persisted?) # prune unpersisted executions
|
86
87
|
|
87
|
-
|
88
|
-
|
89
|
-
|
88
|
+
if execute_inline?
|
89
|
+
inline_executions = executions.select { |execution| (execution.scheduled_at.nil? || execution.scheduled_at <= Time.current) }
|
90
|
+
inline_executions.each(&:advisory_lock!)
|
91
|
+
end
|
90
92
|
end
|
91
|
-
end
|
92
93
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
94
|
+
begin
|
95
|
+
until inline_executions.empty?
|
96
|
+
begin
|
97
|
+
inline_execution = inline_executions.shift
|
98
|
+
inline_result = inline_execution.perform
|
99
|
+
ensure
|
100
|
+
inline_execution.advisory_unlock
|
101
|
+
inline_execution.run_callbacks(:perform_unlocked)
|
102
|
+
end
|
103
|
+
raise inline_result.unhandled_error if inline_result.unhandled_error
|
101
104
|
end
|
102
|
-
|
105
|
+
ensure
|
106
|
+
inline_executions.each(&:advisory_unlock)
|
103
107
|
end
|
104
|
-
ensure
|
105
|
-
inline_executions.each(&:advisory_unlock)
|
106
|
-
end
|
107
108
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
109
|
+
non_inline_executions = executions.reject(&:finished_at)
|
110
|
+
if non_inline_executions.any?
|
111
|
+
job_id_to_active_jobs = active_jobs.index_by(&:job_id)
|
112
|
+
non_inline_executions.group_by(&:queue_name).each do |queue_name, executions_by_queue|
|
113
|
+
executions_by_queue.group_by(&:scheduled_at).each do |scheduled_at, executions_by_queue_and_scheduled_at|
|
114
|
+
state = { queue_name: queue_name, count: executions_by_queue_and_scheduled_at.size }
|
115
|
+
state[:scheduled_at] = scheduled_at if scheduled_at
|
116
|
+
|
117
|
+
executed_locally = execute_async? && @capsule&.create_thread(state)
|
118
|
+
unless executed_locally
|
119
|
+
state[:count] = job_id_to_active_jobs.values_at(*executions_by_queue_and_scheduled_at.map(&:active_job_id)).count { |active_job| send_notify?(active_job) }
|
120
|
+
Notifier.notify(state) unless state[:count].zero?
|
121
|
+
end
|
120
122
|
end
|
121
123
|
end
|
122
124
|
end
|
@@ -137,40 +139,42 @@ module GoodJob
|
|
137
139
|
# job there to be enqueued using enqueue_all
|
138
140
|
return if GoodJob::Bulk.capture(active_job, queue_adapter: self)
|
139
141
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
142
|
+
Rails.application.reloader.wrap do
|
143
|
+
will_execute_inline = execute_inline? && (scheduled_at.nil? || scheduled_at <= Time.current)
|
144
|
+
execution = GoodJob::Execution.enqueue(
|
145
|
+
active_job,
|
146
|
+
scheduled_at: scheduled_at,
|
147
|
+
create_with_advisory_lock: will_execute_inline
|
148
|
+
)
|
146
149
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
150
|
+
if will_execute_inline
|
151
|
+
begin
|
152
|
+
result = execution.perform
|
153
|
+
ensure
|
154
|
+
execution.advisory_unlock
|
155
|
+
execution.run_callbacks(:perform_unlocked)
|
156
|
+
end
|
157
|
+
raise result.unhandled_error if result.unhandled_error
|
158
|
+
else
|
159
|
+
job_state = { queue_name: execution.queue_name }
|
160
|
+
job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
|
161
|
+
|
162
|
+
executed_locally = execute_async? && @capsule&.create_thread(job_state)
|
163
|
+
Notifier.notify(job_state) if !executed_locally && send_notify?(active_job)
|
153
164
|
end
|
154
|
-
raise result.unhandled_error if result.unhandled_error
|
155
|
-
else
|
156
|
-
job_state = { queue_name: execution.queue_name }
|
157
|
-
job_state[:scheduled_at] = execution.scheduled_at if execution.scheduled_at
|
158
165
|
|
159
|
-
|
160
|
-
Notifier.notify(job_state) if !executed_locally && send_notify?(active_job)
|
166
|
+
execution
|
161
167
|
end
|
162
|
-
|
163
|
-
execution
|
164
168
|
end
|
165
169
|
|
166
170
|
# Shut down the thread pool executors.
|
167
|
-
# @param timeout [nil, Numeric,
|
171
|
+
# @param timeout [nil, Numeric, NONE] Seconds to wait for active threads.
|
168
172
|
# * +nil+ trigger a shutdown but not wait for it to complete.
|
169
173
|
# * +-1+ wait until the shutdown is complete.
|
170
174
|
# * +0+ immediately shutdown and stop any threads.
|
171
175
|
# * A positive number will wait that many seconds before stopping any remaining active threads.
|
172
176
|
# @return [void]
|
173
|
-
def shutdown(timeout:
|
177
|
+
def shutdown(timeout: NONE)
|
174
178
|
@capsule&.shutdown(timeout: timeout)
|
175
179
|
@_async_started = false
|
176
180
|
end
|
data/lib/good_job/capsule.rb
CHANGED
@@ -44,23 +44,23 @@ module GoodJob
|
|
44
44
|
end
|
45
45
|
|
46
46
|
# Shut down the thread pool executors.
|
47
|
-
# @param timeout [nil, Numeric,
|
47
|
+
# @param timeout [nil, Numeric, NONE] Seconds to wait for active threads.
|
48
48
|
# * +-1+ will wait for all active threads to complete.
|
49
49
|
# * +0+ will interrupt active threads.
|
50
50
|
# * +N+ will wait at most N seconds and then interrupt active threads.
|
51
51
|
# * +nil+ will trigger a shutdown but not wait for it to complete.
|
52
52
|
# @return [void]
|
53
|
-
def shutdown(timeout:
|
54
|
-
timeout = @configuration.shutdown_timeout if timeout ==
|
53
|
+
def shutdown(timeout: NONE)
|
54
|
+
timeout = @configuration.shutdown_timeout if timeout == NONE
|
55
55
|
GoodJob._shutdown_all([@shared_executor, @notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
|
56
56
|
@startable = false
|
57
57
|
@running = false
|
58
58
|
end
|
59
59
|
|
60
60
|
# Shutdown and then start the capsule again.
|
61
|
-
# @param timeout [Numeric,
|
61
|
+
# @param timeout [Numeric, NONE] Seconds to wait for active threads.
|
62
62
|
# @return [void]
|
63
|
-
def restart(timeout:
|
63
|
+
def restart(timeout: NONE)
|
64
64
|
raise ArgumentError, "Capsule#restart cannot be called with a timeout of nil" if timeout.nil?
|
65
65
|
|
66
66
|
shutdown(timeout: timeout)
|
data/lib/good_job/cli.rb
CHANGED
@@ -17,6 +17,9 @@ module GoodJob
|
|
17
17
|
# Requiring this loads the application's configuration and classes.
|
18
18
|
RAILS_ENVIRONMENT_RB = File.expand_path("config/environment.rb")
|
19
19
|
|
20
|
+
# Number of seconds between checking shutdown conditions
|
21
|
+
SHUTDOWN_EVENT_TIMEOUT = 10
|
22
|
+
|
20
23
|
class << self
|
21
24
|
# Whether the CLI is running from the executable
|
22
25
|
# @return [Boolean, nil]
|
@@ -106,14 +109,15 @@ module GoodJob
|
|
106
109
|
probe_server.start
|
107
110
|
end
|
108
111
|
|
109
|
-
|
112
|
+
require 'concurrent/atomic/event'
|
113
|
+
@stop_good_job_executable = Concurrent::Event.new
|
110
114
|
%w[INT TERM].each do |signal|
|
111
|
-
trap(signal) { @stop_good_job_executable
|
115
|
+
trap(signal) { Thread.new { @stop_good_job_executable.set }.join }
|
112
116
|
end
|
113
117
|
|
114
118
|
Kernel.loop do
|
115
|
-
|
116
|
-
break if @stop_good_job_executable || capsule.shutdown?
|
119
|
+
@stop_good_job_executable.wait(SHUTDOWN_EVENT_TIMEOUT)
|
120
|
+
break if @stop_good_job_executable.set? || capsule.shutdown?
|
117
121
|
end
|
118
122
|
|
119
123
|
systemd.stop do
|
data/lib/good_job/version.rb
CHANGED
data/lib/good_job.rb
CHANGED
@@ -42,6 +42,10 @@ require "good_job/systemd_service"
|
|
42
42
|
module GoodJob
|
43
43
|
include GoodJob::Dependencies
|
44
44
|
|
45
|
+
# Default, null, blank value placeholder.
|
46
|
+
NONE = Module.new.freeze
|
47
|
+
|
48
|
+
# Default logger for GoodJob; overridden by Rails.logger in Railtie.
|
45
49
|
DEFAULT_LOGGER = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
|
46
50
|
|
47
51
|
# @!attribute [rw] active_record_parent_class
|
@@ -233,13 +237,19 @@ module GoodJob
|
|
233
237
|
# This is primarily intended for usage in a test environment.
|
234
238
|
# Unhandled job errors will be raised.
|
235
239
|
# @param queue_string [String] Queues to execute jobs from
|
240
|
+
# @param limit [Integer, nil] Maximum number of iterations for the loop
|
236
241
|
# @return [void]
|
237
|
-
def self.perform_inline(queue_string = "*")
|
242
|
+
def self.perform_inline(queue_string = "*", limit: nil)
|
238
243
|
job_performer = JobPerformer.new(queue_string)
|
244
|
+
iteration = 0
|
239
245
|
loop do
|
240
|
-
|
246
|
+
break if limit && iteration >= limit
|
247
|
+
|
248
|
+
result = Rails.application.reloader.wrap { job_performer.next }
|
241
249
|
break unless result
|
242
250
|
raise result.unhandled_error if result.unhandled_error
|
251
|
+
|
252
|
+
iteration += 1
|
243
253
|
end
|
244
254
|
end
|
245
255
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Sheldon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-11-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -384,7 +384,7 @@ licenses:
|
|
384
384
|
metadata:
|
385
385
|
bug_tracker_uri: https://github.com/bensheldon/good_job/issues
|
386
386
|
changelog_uri: https://github.com/bensheldon/good_job/blob/master/CHANGELOG.md
|
387
|
-
documentation_uri: https://
|
387
|
+
documentation_uri: https://rubydoc.info/gems/good_job
|
388
388
|
homepage_uri: https://github.com/bensheldon/good_job
|
389
389
|
source_code_uri: https://github.com/bensheldon/good_job
|
390
390
|
rubygems_mfa_required: 'true'
|