good_job 3.25.0 → 3.26.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 47f288c089d005a2b97e5fe6d7bd8fb7094d48fb52565bacf0493b5e547f6516
4
- data.tar.gz: cf8cd69a7a1f64fee5172b7fd3b184d774eed0cd48d97ba0d01971af0d721646
3
+ metadata.gz: a51ff44544ae7fb381fce50f13d3196d6a561bcdc6a5af7f37ad1e8fb0d6b258
4
+ data.tar.gz: 3703fe631d3f549bb152999203e5d67ac6d305c4ac77bba5b47621f3419cef21
5
5
  SHA512:
6
- metadata.gz: 3c74189b7315da1fcb5747b285cb4d7837550dcf1456899f2d6f023065a893bd99c93a7e292415b6796a64232335a732666a7783fc04f2319968fff4bfaf7dcc
7
- data.tar.gz: 67fd833bfa126355ef0853263c7e72b5bfa5d013e1061bc128480408a52b49eddf9724686e3dc47293f8713696074f8029452c035159aee50940f0b320ff36ab
6
+ metadata.gz: ba6647af970ee04455977ccdfb835ac03648bc544ae3606e69e2007d04bf13634f0767c1263738d04101a6839949affd3227a19b1b573d27a6cb8bb4ea56a5b6
7
+ data.tar.gz: 3c8e79ae13f69d050f35aea95ebaf4c035a97138a89d24c57430776909d44c9fbbd9a5ab4460ca30efd65c2c16ec231780957c555f43a77dc100388dbfffb341
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.26.0](https://github.com/bensheldon/good_job/tree/v3.26.0) (2024-03-01)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.25.0...v3.26.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Add `GoodJob.current_thread_running?` and `GoodJob.current_thread_shutting_down?` for graceful shutdowns [\#1253](https://github.com/bensheldon/good_job/pull/1253) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - Ensure "shutdown?" behavior is consistent between J Ruby and C Ruby [\#1267](https://github.com/bensheldon/good_job/pull/1267) ([bensheldon](https://github.com/bensheldon))
14
+
15
+ **Closed issues:**
16
+
17
+ - PG Good Job rows add up [\#1262](https://github.com/bensheldon/good_job/issues/1262)
18
+ - Bulk operations do not work for Batches [\#1255](https://github.com/bensheldon/good_job/issues/1255)
19
+ - What's the difference between 'reschedule' and 'retry' in the dashboard? [\#1241](https://github.com/bensheldon/good_job/issues/1241)
20
+
21
+ **Merged pull requests:**
22
+
23
+ - feat: add italian locale [\#1268](https://github.com/bensheldon/good_job/pull/1268) ([metalelf0](https://github.com/metalelf0))
24
+
3
25
  ## [v3.25.0](https://github.com/bensheldon/good_job/tree/v3.25.0) (2024-02-22)
4
26
 
5
27
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.24.0...v3.25.0)
data/README.md CHANGED
@@ -57,7 +57,7 @@ For more of the story of GoodJob, read the [introductory blog post](https://isla
57
57
  - [Exceptions](#exceptions)
58
58
  - [Retries](#retries)
59
59
  - [Action Mailer retries](#action-mailer-retries)
60
- - [Interrupts](#interrupts)
60
+ - [Interrupts, graceful shutdown, and SIGKILL](#Interrupts-graceful-shutdown-and-SIGKILL)
61
61
  - [Timeouts](#timeouts)
62
62
  - [Optimize queues, threads, and processes](#optimize-queues-threads-and-processes)
63
63
  - [Database connections](#database-connections)
@@ -974,9 +974,24 @@ end
974
974
  Note, that `ActionMailer::MailDeliveryJob` is a default since Rails 6.0. Be sure that your app is using that class, as it
975
975
  might also be configured to use (deprecated now) `ActionMailer::DeliveryJob`.
976
976
 
977
- ### Interrupts
977
+ ### Interrupts, graceful shutdown, and SIGKILL
978
978
 
979
- Jobs will be automatically retried if the process is interrupted while performing a job, for example as the result of a `SIGKILL` or power failure.
979
+ When GoodJob receives an interrupt (SIGINT, SIGTERM) or explicitly with `GoodJob.shutdown`, GoodJob will attempt to gracefully shut down, waiting for all jobs to finish before exiting based on the `shutdown_timeout` configuration.
980
+
981
+ To detect the start of a graceful shutdown from within a performing job, for example while looping/iterating over multiple items, you can call `GoodJob.current_thread_shutting_down?` or `GoodJob.current_thread_running?` from within the job. For example:
982
+
983
+ ```ruby
984
+ def perform(lots_of_records)
985
+ lots_of_records.each do |record|
986
+ break if GoodJob.current_thread_shutting_down? # or `unless GoodJob.current_thread.running?`
987
+ # process record ...
988
+ end
989
+ end
990
+ ````
991
+
992
+ Note that when running jobs in `:inline` execution mode, `GoodJob.current_thread_running?` will always be truthy and `GoodJob.current_thread_shutting_down?` will always be falsey.
993
+
994
+ Jobs will be automatically retried if the process is interrupted while performing a job and the job is unable to finish before the timeout or as the result of a `SIGKILL` or power failure.
980
995
 
981
996
  If you need more control over interrupt-caused retries, include the `GoodJob::ActiveJobExtensions::InterruptErrors` extension in your job class. When an interrupted job is retried, the extension will raise a `GoodJob::InterruptError` exception within the job, which allows you to use Active Job's `retry_on` and `discard_on` to control the behavior of the job.
982
997
 
@@ -0,0 +1,243 @@
1
+ ---
2
+ it:
3
+ good_job:
4
+ actions:
5
+ destroy: Elimina
6
+ discard: Scarta
7
+ force_discard: Forza scarto
8
+ inspect: Ispeziona
9
+ reschedule: Riprogramma
10
+ retry: Riprova
11
+ batches:
12
+ index:
13
+ older_batches: Batch più vecchi
14
+ pending_migrations: GoodJob ha migrazioni del database in sospeso.
15
+ title: Batch
16
+ jobs:
17
+ actions:
18
+ confirm_destroy: Sei sicuro di voler eliminare questo job?
19
+ confirm_discard: Sei sicuro di voler scartare questo job?
20
+ confirm_reschedule: Sei sicuro di voler riprogrammare questo job?
21
+ confirm_retry: Sei sicuro di voler riprovare questo job?
22
+ destroy: Elimina job
23
+ discard: Scarta job
24
+ reschedule: Riprogramma job
25
+ retry: Riprova job
26
+ title: Azioni
27
+ no_jobs_found: Nessun job trovato.
28
+ show:
29
+ attributes: Attributi
30
+ batched_jobs: Job raggruppati
31
+ callback_jobs: Job di callback
32
+ table:
33
+ no_batches_found: Nessun batch trovato.
34
+ cron_entries:
35
+ actions:
36
+ confirm_disable: Sei sicuro di voler disabilitare questa voce cron?
37
+ confirm_enable: Sei sicuro di voler confermare questa voce cron?
38
+ confirm_enqueue: Sei sicuro di voler mettere in coda questa voce cron?
39
+ disable: Disabilita voce cron
40
+ enable: Abilita voce cron
41
+ enqueue: Metti in coda voce cron ora
42
+ disable:
43
+ notice: Voce cron è stata disabilitata.
44
+ enable:
45
+ notice: Voce cron è stata abilitata.
46
+ enqueue:
47
+ notice: Voce cron è stata messa in coda.
48
+ index:
49
+ no_cron_schedules_found: Nessuna pianificazione cron trovata.
50
+ title: Pianificazioni cron
51
+ pending_migrations: Richiede migrazione del database GoodJob in sospeso.
52
+ show:
53
+ cron_entry_key: Chiave voce cron
54
+ datetime:
55
+ distance_in_words:
56
+ about_x_hours:
57
+ one: circa 1 ora
58
+ other: circa %{count} ore
59
+ about_x_months:
60
+ one: circa 1 mese
61
+ other: circa %{count} mesi
62
+ about_x_years:
63
+ one: circa 1 anno
64
+ other: circa %{count} anni
65
+ almost_x_years:
66
+ one: quasi 1 anno
67
+ other: quasi %{count} anni
68
+ half_a_minute: mezzo minuto
69
+ less_than_x_minutes:
70
+ one: meno di un minuto
71
+ other: meno di %{count} minuti
72
+ less_than_x_seconds:
73
+ one: meno di 1 secondo
74
+ other: meno di %{count} secondi
75
+ over_x_years:
76
+ one: più di 1 anno
77
+ other: più di %{count} anni
78
+ x_days:
79
+ one: 1 giorno
80
+ other: "%{count} giorni"
81
+ x_minutes:
82
+ one: 1 minuto
83
+ other: "%{count} minuti"
84
+ x_months:
85
+ one: 1 mese
86
+ other: "%{count} mesi"
87
+ x_seconds:
88
+ one: 1 secondo
89
+ other: "%{count} secondi"
90
+ x_years:
91
+ one: 1 anno
92
+ other: "%{count} anni"
93
+ duration:
94
+ hours: "%{hour}h %{min}m"
95
+ less_than_10_seconds: "%{sec}s"
96
+ milliseconds: "%{ms}ms"
97
+ minutes: "%{min}m %{sec}s"
98
+ seconds: "%{sec}s"
99
+ error_event:
100
+ discarded: Scartato
101
+ handled: Gestito
102
+ interrupted: Interrotto
103
+ retried: Riprovato
104
+ retry_stopped: Interrotto riprova
105
+ unhandled: Non gestito
106
+ helpers:
107
+ relative_time:
108
+ future: tra %{time}
109
+ past: "%{time} fa"
110
+ jobs:
111
+ actions:
112
+ confirm_destroy: Sei sicuro di voler eliminare il job?
113
+ confirm_discard: Sei sicuro di voler scartare il job?
114
+ confirm_force_discard: 'Sei sicuro di voler forzare lo scarto di questo job? Il job verrà contrassegnato come scartato ma il job in esecuzione non verrà interrotto - tuttavia, non verrà riprovato in caso di fallimento.
115
+
116
+ '
117
+ confirm_reschedule: Sei sicuro di voler riprogrammare il job?
118
+ confirm_retry: Sei sicuro di voler riprovare il job?
119
+ destroy: Elimina job
120
+ discard: Scarta job
121
+ force_discard: Forza scarto job
122
+ reschedule: Riprogramma job
123
+ retry: Riprova job
124
+ destroy:
125
+ notice: Il job è stato eliminato
126
+ discard:
127
+ notice: Il job è stato scartato
128
+ executions:
129
+ in_queue: in coda
130
+ runtime: tempo di esecuzione
131
+ title: Esecuzioni
132
+ force_discard:
133
+ notice: Il job è stato forzatamente scartato. Continuerà ad eseguirsi ma non verrà riprovato in caso di fallimento
134
+ index:
135
+ job_pagination: Paginazione job
136
+ older_jobs: Job più vecchi
137
+ reschedule:
138
+ notice: Il job è stato riprogrammato
139
+ retry:
140
+ notice: Il job è stato riprovato
141
+ show:
142
+ jobs: Job
143
+ table:
144
+ actions:
145
+ apply_to_all:
146
+ one: Applica al job (1).
147
+ other: Applica a tutti %{count} i job.
148
+ confirm_destroy_all: Sei sicuro di voler eliminare i job selezionati?
149
+ confirm_discard_all: Sei sicuro di voler scartare i job selezionati?
150
+ confirm_reschedule_all: Sei sicuro di voler riprogrammare i job selezionati?
151
+ confirm_retry_all: Sei sicuro di voler riprovare i job selezionati?
152
+ destroy_all: Elimina tutti
153
+ discard_all: Scarta tutti
154
+ reschedule_all: Riprogramma tutti
155
+ retry_all: Riprova tutti
156
+ title: Azioni
157
+ no_jobs_found: Nessun job trovato.
158
+ toggle_actions: Attiva/Disattiva Azioni
159
+ toggle_all_jobs: Attiva/Disattiva tutti i job
160
+ models:
161
+ batch:
162
+ created: Creato
163
+ created_at: Creato il
164
+ discarded: Scartato
165
+ discarded_at: Scartato il
166
+ enqueued: In coda
167
+ enqueued_at: In coda il
168
+ finished: Finito
169
+ finished_at: Finito il
170
+ jobs: Job
171
+ name: Nome
172
+ cron:
173
+ class: Classe
174
+ last_run: Ultima esecuzione
175
+ next_scheduled: Prossima pianificazione
176
+ schedule: Pianificazione
177
+ job:
178
+ arguments: Argomenti
179
+ attempts: Tentativi
180
+ priority: Priorità
181
+ queue: Coda
182
+ number:
183
+ format:
184
+ delimiter: ","
185
+ separator: "."
186
+ human:
187
+ decimal_units:
188
+ delimiter: ","
189
+ format: "%n%u"
190
+ precision: 3
191
+ separator: "."
192
+ units:
193
+ billion: B
194
+ million: M
195
+ quadrillion: Q
196
+ thousand: K
197
+ trillion: T
198
+ unit: ''
199
+ processes:
200
+ index:
201
+ cron_enabled: Cron abilitato
202
+ no_good_job_processes_found: Nessun processo GoodJob trovato.
203
+ process: Processo
204
+ schedulers: Schedulers
205
+ started: Avviato
206
+ title: Processi
207
+ updated: Aggiornato
208
+ shared:
209
+ boolean:
210
+ 'false': 'No'
211
+ 'true': Sì
212
+ error: Errore
213
+ filter:
214
+ all: Tutti
215
+ all_jobs: Tutti i job
216
+ all_queues: Tutte le code
217
+ clear: Cancella
218
+ job_name: Nome job
219
+ placeholder: Cerca per classe, ID job, parametri job e testo errore.
220
+ queue_name: Nome coda
221
+ search: Cerca
222
+ navbar:
223
+ batches: Batch
224
+ cron_schedules: Cron
225
+ jobs: Job
226
+ live_poll: Live Poll
227
+ name: "GoodJob 👍"
228
+ processes: Processi
229
+ theme:
230
+ auto: Auto
231
+ dark: Scuro
232
+ light: Chiaro
233
+ theme: Tema
234
+ secondary_navbar:
235
+ inspiration: Ricorda, stai facendo anche tu un Buon Lavoro!
236
+ last_updated: Ultimo aggiornamento
237
+ status:
238
+ discarded: Scartato
239
+ queued: In coda
240
+ retried: Riprovato
241
+ running: In esecuzione
242
+ scheduled: Pianificato
243
+ succeeded: Riuscito
@@ -78,7 +78,7 @@ module GoodJob
78
78
  # @return [void]
79
79
  def self.reset(values = {})
80
80
  ACCESSORS.each do |accessor|
81
- send("#{accessor}=", values[accessor])
81
+ send(:"#{accessor}=", values[accessor])
82
82
  end
83
83
  end
84
84
 
@@ -93,7 +93,7 @@ module GoodJob # :nodoc:
93
93
  if timeout.nil?
94
94
  @connected.set?
95
95
  else
96
- @connected.wait(timeout == -1 ? nil : timeout)
96
+ @connected.wait(timeout&.negative? ? nil : timeout)
97
97
  end
98
98
  end
99
99
 
@@ -104,7 +104,7 @@ module GoodJob # :nodoc:
104
104
  if timeout.nil?
105
105
  @listening.set?
106
106
  else
107
- @listening.wait(timeout == -1 ? nil : timeout)
107
+ @listening.wait(timeout&.negative? ? nil : timeout)
108
108
  end
109
109
  end
110
110
 
@@ -130,7 +130,7 @@ module GoodJob # :nodoc:
130
130
  @listening.reset
131
131
  @shutdown_event.set
132
132
  else
133
- @shutdown_event.wait(timeout == -1 ? nil : timeout) unless timeout.nil?
133
+ @shutdown_event.wait(timeout&.negative? ? nil : timeout) unless timeout.nil?
134
134
  @connected.reset if @shutdown_event.set?
135
135
  end
136
136
  @shutdown_event.set?
@@ -71,9 +71,11 @@ module GoodJob # :nodoc:
71
71
  # @return [Boolean, nil]
72
72
  delegate :running?, to: :executor, allow_nil: true
73
73
 
74
- # Tests whether the scheduler is shutdown.
74
+ # Tests whether the scheduler is shutdown and no tasks are running.
75
75
  # @return [Boolean, nil]
76
- delegate :shutdown?, to: :executor, allow_nil: true
76
+ def shutdown?
77
+ @executor.nil? || (executor.shutdown? && !executor.shuttingdown?)
78
+ end
77
79
 
78
80
  # Shut down the scheduler.
79
81
  # This stops all threads in the thread pool.
@@ -85,7 +87,7 @@ module GoodJob # :nodoc:
85
87
  # * A positive number will wait that many seconds before stopping any remaining active tasks.
86
88
  # @return [void]
87
89
  def shutdown(timeout: -1)
88
- return if executor.nil? || executor.shutdown?
90
+ return if executor.nil? || (executor.shutdown? && !executor.shuttingdown?)
89
91
 
90
92
  instrument("scheduler_shutdown_start", { timeout: timeout })
91
93
  instrument("scheduler_shutdown", { timeout: timeout }) do
@@ -96,11 +98,11 @@ module GoodJob # :nodoc:
96
98
 
97
99
  if executor.shuttingdown? && timeout
98
100
  executor_wait = timeout.negative? ? nil : timeout
101
+ return if executor.wait_for_termination(executor_wait)
99
102
 
100
- unless executor.wait_for_termination(executor_wait)
101
- instrument("scheduler_shutdown_kill", { active_job_ids: @performer.performing_active_job_ids.to_a })
102
- executor.kill
103
- end
103
+ instrument("scheduler_shutdown_kill", { active_job_ids: @performer.performing_active_job_ids.to_a })
104
+ executor.kill
105
+ executor.wait_for_termination
104
106
  end
105
107
  end
106
108
  end
@@ -268,6 +270,8 @@ module GoodJob # :nodoc:
268
270
  def create_task(delay = 0, fanout: false)
269
271
  future = Concurrent::ScheduledTask.new(delay, args: [self, performer], executor: executor, timer_set: timer_set) do |thr_scheduler, thr_performer|
270
272
  Thread.current.name = Thread.current.name.sub("-worker-", "-thread-") if Thread.current.name
273
+ Thread.current[:good_job_scheduler] = thr_scheduler
274
+
271
275
  Rails.application.reloader.wrap do
272
276
  thr_performer.next do |found|
273
277
  thr_scheduler.create_thread({ fanout: fanout }) if found && fanout
@@ -21,12 +21,10 @@ module GoodJob
21
21
  @executor&.running?
22
22
  end
23
23
 
24
+ # Tests whether the scheduler is shutdown and no tasks are running.
25
+ # @return [Boolean, nil]
24
26
  def shutdown?
25
- if @executor
26
- @executor.shutdown?
27
- else
28
- true
29
- end
27
+ @executor.nil? || (@executor.shutdown? && !@executor.shuttingdown?)
30
28
  end
31
29
 
32
30
  # Shut down the SharedExecutor.
@@ -38,13 +36,16 @@ module GoodJob
38
36
  # * A positive number will wait that many seconds before stopping any remaining active threads.
39
37
  # @return [void]
40
38
  def shutdown(timeout: -1)
41
- return if @executor.nil? || @executor.shutdown?
39
+ return if @executor.nil? || (@executor.shutdown? && !@executor.shuttingdown?)
42
40
 
43
41
  @executor.shutdown if @executor.running?
44
42
 
45
43
  if @executor.shuttingdown? && timeout # rubocop:disable Style/GuardClause
46
44
  executor_wait = timeout.negative? ? nil : timeout
47
- @executor.kill unless @executor.wait_for_termination(executor_wait)
45
+ return if @executor.wait_for_termination(executor_wait)
46
+
47
+ @executor.kill
48
+ @executor.wait_for_termination
48
49
  end
49
50
  end
50
51
 
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob
4
+ # Provides methods for determining the status of the
5
+ # current job execution thread. This is useful for determining
6
+ # whether to continue processing a job or to shut down gracefully.
7
+ module ThreadStatus
8
+ extend ActiveSupport::Concern
9
+
10
+ class_methods do
11
+ # Whether the current job execution thread is in a running state.
12
+ # @return [Boolean]
13
+ def current_thread_running?
14
+ scheduler = Thread.current[:good_job_scheduler]
15
+ scheduler ? scheduler.running? : true
16
+ end
17
+
18
+ # Whether the current job execution thread is shutting down
19
+ # (the opposite of running).
20
+ # @return [Boolean]
21
+ def current_thread_shutting_down?
22
+ scheduler = Thread.current[:good_job_scheduler]
23
+ scheduler && !scheduler.running?
24
+ end
25
+ end
26
+ end
27
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '3.25.0'
5
+ VERSION = '3.26.0'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
data/lib/good_job.rb CHANGED
@@ -40,12 +40,14 @@ require "good_job/probe_server/webrick_handler"
40
40
  require "good_job/scheduler"
41
41
  require "good_job/shared_executor"
42
42
  require "good_job/systemd_service"
43
+ require "good_job/thread_status"
43
44
 
44
45
  # GoodJob is a multithreaded, Postgres-based, ActiveJob backend for Ruby on Rails.
45
46
  #
46
47
  # +GoodJob+ is the top-level namespace and exposes configuration attributes.
47
48
  module GoodJob
48
49
  include GoodJob::Dependencies
50
+ include GoodJob::ThreadStatus
49
51
 
50
52
  # Default, null, blank value placeholder.
51
53
  NONE = Module.new.freeze
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.25.0
4
+ version: 3.26.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Sheldon
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-02-22 00:00:00.000000000 Z
11
+ date: 2024-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -347,6 +347,7 @@ files:
347
347
  - config/locales/en.yml
348
348
  - config/locales/es.yml
349
349
  - config/locales/fr.yml
350
+ - config/locales/it.yml
350
351
  - config/locales/ja.yml
351
352
  - config/locales/ko.yml
352
353
  - config/locales/nl.yml
@@ -407,6 +408,7 @@ files:
407
408
  - lib/good_job/sd_notify.rb
408
409
  - lib/good_job/shared_executor.rb
409
410
  - lib/good_job/systemd_service.rb
411
+ - lib/good_job/thread_status.rb
410
412
  - lib/good_job/version.rb
411
413
  homepage: https://github.com/bensheldon/good_job
412
414
  licenses: