good_job 3.15.13 → 3.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +56 -4
  3. data/README.md +27 -10
  4. data/app/controllers/good_job/application_controller.rb +16 -9
  5. data/app/controllers/good_job/batches_controller.rb +1 -0
  6. data/app/controllers/good_job/cron_entries_controller.rb +2 -1
  7. data/app/controllers/good_job/frontends_controller.rb +1 -0
  8. data/app/controllers/good_job/jobs_controller.rb +1 -0
  9. data/app/controllers/good_job/processes_controller.rb +1 -0
  10. data/app/filters/good_job/base_filter.rb +1 -0
  11. data/app/filters/good_job/batches_filter.rb +1 -0
  12. data/app/filters/good_job/jobs_filter.rb +1 -0
  13. data/app/helpers/good_job/application_helper.rb +1 -1
  14. data/app/models/concerns/good_job/error_events.rb +28 -0
  15. data/app/models/concerns/good_job/filterable.rb +1 -0
  16. data/app/models/{good_job → concerns/good_job}/lockable.rb +1 -0
  17. data/app/models/concerns/good_job/reportable.rb +1 -0
  18. data/app/models/good_job/active_record_parent_class.rb +9 -0
  19. data/app/models/good_job/base_execution.rb +10 -1
  20. data/app/models/good_job/base_record.rb +4 -1
  21. data/app/models/good_job/cron_entry.rb +1 -0
  22. data/app/models/good_job/discrete_execution.rb +9 -0
  23. data/app/models/good_job/execution.rb +41 -8
  24. data/app/models/good_job/execution_result.rb +17 -2
  25. data/app/models/good_job/i18n_config.rb +25 -0
  26. data/app/models/good_job/job.rb +4 -1
  27. data/app/models/good_job/process.rb +51 -18
  28. data/app/views/good_job/jobs/_executions.erb +1 -1
  29. data/app/views/good_job/jobs/_table.erb +17 -4
  30. data/app/views/good_job/processes/index.html.erb +6 -2
  31. data/app/views/good_job/shared/_navbar.erb +1 -1
  32. data/config/locales/de.yml +9 -1
  33. data/config/locales/en.yml +9 -1
  34. data/config/locales/es.yml +9 -1
  35. data/config/locales/fr.yml +9 -1
  36. data/config/locales/ja.yml +9 -1
  37. data/config/locales/nl.yml +9 -1
  38. data/config/locales/ru.yml +9 -1
  39. data/config/locales/tr.yml +227 -0
  40. data/config/locales/{ua.yml → uk.yml} +34 -2
  41. data/config/routes.rb +1 -0
  42. data/exe/good_job +1 -0
  43. data/lib/active_job/queue_adapters/good_job_adapter.rb +1 -0
  44. data/lib/generators/good_job/install_generator.rb +1 -0
  45. data/lib/generators/good_job/templates/install/migrations/create_good_jobs.rb.erb +5 -1
  46. data/lib/generators/good_job/templates/update/migrations/01_create_good_jobs.rb.erb +3 -1
  47. data/lib/generators/good_job/templates/update/migrations/02_create_good_job_settings.rb.erb +1 -0
  48. data/lib/generators/good_job/templates/update/migrations/03_create_index_good_jobs_jobs_on_priority_created_at_when_unfinished.rb.erb +1 -0
  49. data/lib/generators/good_job/templates/update/migrations/04_create_good_job_batches.rb.erb +1 -0
  50. data/lib/generators/good_job/templates/update/migrations/05_create_good_job_executions.rb.erb +1 -0
  51. data/lib/generators/good_job/templates/update/migrations/06_create_good_jobs_error_event.rb.erb +16 -0
  52. data/lib/generators/good_job/update_generator.rb +1 -0
  53. data/lib/good_job/active_job_extensions/batches.rb +1 -0
  54. data/lib/good_job/active_job_extensions/concurrency.rb +2 -1
  55. data/lib/good_job/active_job_extensions/interrupt_errors.rb +1 -0
  56. data/lib/good_job/active_job_extensions/notify_options.rb +1 -0
  57. data/lib/good_job/adapter.rb +3 -2
  58. data/lib/good_job/assignable_connection.rb +1 -0
  59. data/lib/good_job/bulk.rb +1 -0
  60. data/lib/good_job/capsule.rb +5 -4
  61. data/lib/good_job/cleanup_tracker.rb +2 -1
  62. data/lib/good_job/cli.rb +1 -0
  63. data/lib/good_job/configuration.rb +7 -0
  64. data/lib/good_job/cron_manager.rb +3 -3
  65. data/lib/good_job/current_thread.rb +8 -0
  66. data/lib/good_job/daemon.rb +1 -0
  67. data/lib/good_job/engine.rb +13 -0
  68. data/lib/good_job/interrupt_error.rb +1 -0
  69. data/lib/good_job/job_performer.rb +1 -0
  70. data/lib/good_job/log_subscriber.rb +1 -0
  71. data/lib/good_job/metrics.rb +57 -0
  72. data/lib/good_job/multi_scheduler.rb +1 -0
  73. data/lib/good_job/notifier/{process_registration.rb → process_heartbeat.rb} +10 -1
  74. data/lib/good_job/notifier.rb +18 -16
  75. data/lib/good_job/poller.rb +3 -3
  76. data/lib/good_job/scheduler.rb +30 -11
  77. data/lib/good_job/version.rb +2 -1
  78. data/lib/good_job.rb +15 -1
  79. metadata +11 -5
@@ -1,5 +1,5 @@
1
1
  ---
2
- ua:
2
+ uk:
3
3
  good_job:
4
4
  actions:
5
5
  destroy: Видалити
@@ -53,40 +53,64 @@ ua:
53
53
  datetime:
54
54
  distance_in_words:
55
55
  about_x_hours:
56
+ few: близько %{count} годин
57
+ many: близько %{count} годин
56
58
  one: близько 1 години
57
59
  other: близько %{count} годин
58
60
  about_x_months:
61
+ few: близько %{count} місяців
62
+ many: близько %{count} місяців
59
63
  one: близько 1 місяця
60
64
  other: близько %{count} місяців
61
65
  about_x_years:
66
+ few: близько %{count} років
67
+ many: близько %{count} років
62
68
  one: близько 1 року
63
69
  other: близько %{count} років
64
70
  almost_x_years:
71
+ few: майже %{count} роки
72
+ many: майже %{count} років
65
73
  one: майже 1 рік
66
74
  other: майже %{count} років
67
75
  half_a_minute: півхвилини
68
76
  less_than_x_minutes:
77
+ few: менше %{count} хвилин
78
+ many: менше %{count} хвилин
69
79
  one: менше 1 хвилини
70
80
  other: менше %{count} хвилин
71
81
  less_than_x_seconds:
82
+ few: менше %{count} секунд
83
+ many: менше %{count} секунд
72
84
  one: менше 1 секунди
73
85
  other: менше %{count} секунд
74
86
  over_x_years:
87
+ few: більше %{count} років
88
+ many: більше %{count} років
75
89
  one: понад 1 рік
76
90
  other: понад %{count} років
77
91
  x_days:
92
+ few: "%{count} дні"
93
+ many: "%{count} днів"
78
94
  one: 1 день
79
95
  other: "%{count} днів"
80
96
  x_minutes:
97
+ few: 1 хвилини
98
+ many: "%{count} хвилин"
81
99
  one: 1 хвилина
82
100
  other: "%{count} хвилин"
83
101
  x_months:
102
+ few: "%{count} місяці"
103
+ many: "%{count} місяців"
84
104
  one: 1 місяць
85
105
  other: "%{count} місяців"
86
106
  x_seconds:
107
+ few: "%{count} секунди"
108
+ many: "%{count} секунд"
87
109
  one: 1 секунда
88
110
  other: "%{count} секунд"
89
111
  x_years:
112
+ few: "%{count} роки"
113
+ many: "%{count} років"
90
114
  one: 1 рік
91
115
  other: "%{count} років"
92
116
  duration:
@@ -95,6 +119,13 @@ ua:
95
119
  milliseconds: "%{ms}мс"
96
120
  minutes: "%{min}хв %{sec}с"
97
121
  seconds: "%{sec}с"
122
+ error_event:
123
+ discarded: Відкинуто
124
+ handled: Обробляється
125
+ interrupted: Перерваний
126
+ retried: Повторна спроба
127
+ retry_stopped: Повторну спробу зупинено
128
+ unhandled: Необроблений
98
129
  helpers:
99
130
  relative_time:
100
131
  future: за %{time}
@@ -114,7 +145,6 @@ ua:
114
145
  discard:
115
146
  notice: Завдання було відхилено
116
147
  executions:
117
- error: Помилка
118
148
  in_queue: у черзі
119
149
  runtime: час виконання
120
150
  title: Виконання
@@ -192,7 +222,9 @@ ua:
192
222
  schedulers: Планувальники
193
223
  started: Розпочато
194
224
  title: Процеси
225
+ updated: Оновлено
195
226
  shared:
227
+ error: Помилка
196
228
  filter:
197
229
  all: Всі
198
230
  all_jobs: Усі роботи
data/config/routes.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  GoodJob::Engine.routes.draw do
3
4
  root to: redirect(path: 'jobs')
4
5
 
data/exe/good_job CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
+
3
4
  require 'good_job/cli'
4
5
 
5
6
  GoodJob::CLI.within_exe = true
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module ActiveJob # :nodoc:
3
4
  module QueueAdapters # :nodoc:
4
5
  # See {GoodJob::Adapter} for details.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'rails/generators'
3
4
  require 'rails/generators/active_record'
4
5
 
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
3
4
  def change
4
- enable_extension 'pgcrypto'
5
+ # Uncomment for Postgres v12 or earlier to enable gen_random_uuid() support
6
+ # enable_extension 'pgcrypto'
5
7
 
6
8
  create_table :good_jobs, id: :uuid do |t|
7
9
  t.text :queue_name
@@ -26,6 +28,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
26
28
  t.boolean :is_discrete
27
29
  t.integer :executions_count
28
30
  t.text :job_class
31
+ t.integer :error_event, limit: 2
29
32
  end
30
33
 
31
34
  create_table :good_job_batches, id: :uuid do |t|
@@ -52,6 +55,7 @@ class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
52
55
  t.datetime :scheduled_at
53
56
  t.datetime :finished_at
54
57
  t.text :error
58
+ t.integer :error_event, limit: 2
55
59
  end
56
60
 
57
61
  create_table :good_job_processes, id: :uuid do |t|
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateGoodJobs < ActiveRecord::Migration<%= migration_version %>
3
4
  def change
4
- enable_extension 'pgcrypto'
5
+ # Uncomment for Postgres v12 or earlier to enable gen_random_uuid() support
6
+ # enable_extension 'pgcrypto'
5
7
 
6
8
  create_table :good_jobs, id: :uuid do |t|
7
9
  t.text :queue_name
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateGoodJobSettings < ActiveRecord::Migration<%= migration_version %>
3
4
  def change
4
5
  reversible do |dir|
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateIndexGoodJobsJobsOnPriorityCreatedAtWhenUnfinished < ActiveRecord::Migration<%= migration_version %>
3
4
  disable_ddl_transaction!
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateGoodJobBatches < ActiveRecord::Migration<%= migration_version %>
3
4
  def change
4
5
  reversible do |dir|
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  class CreateGoodJobExecutions < ActiveRecord::Migration<%= migration_version %>
3
4
  def change
4
5
  reversible do |dir|
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateGoodJobsErrorEvent < ActiveRecord::Migration<%= migration_version %>
4
+ def change
5
+ reversible do |dir|
6
+ dir.up do
7
+ # Ensure this incremental update migration is idempotent
8
+ # with monolithic install migration.
9
+ return if connection.column_exists?(:good_jobs, :error_event)
10
+ end
11
+ end
12
+
13
+ add_column :good_jobs, :error_event, :integer, limit: 2
14
+ add_column :good_job_executions, :error_event, :integer, limit: 2
15
+ end
16
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'rails/generators'
3
4
  require 'rails/generators/active_record'
4
5
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  module ActiveJobExtensions
4
5
  module Batches
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  module ActiveJobExtensions
4
5
  module Concurrency
@@ -92,7 +93,7 @@ module GoodJob
92
93
  key = self.class.good_job_concurrency_config[:key]
93
94
  return if key.blank?
94
95
 
95
- key = key.respond_to?(:call) ? instance_exec(&key) : key
96
+ key = instance_exec(&key) if key.respond_to?(:call)
96
97
  raise TypeError, "Concurrency key must be a String; was a #{key.class}" unless VALID_TYPES.any? { |type| key.is_a?(type) }
97
98
 
98
99
  key
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  module ActiveJobExtensions
4
5
  module InterruptErrors
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  module ActiveJobExtensions
4
5
  # Allows configuring whether GoodJob should emit a NOTIFY event when a job is enqueued.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # ActiveJob Adapter.
@@ -8,7 +9,7 @@ module GoodJob
8
9
  # @!scope class
9
10
  # List of all instantiated Adapters in the current process.
10
11
  # @return [Array<GoodJob::Adapter>, nil]
11
- cattr_reader :instances, default: [], instance_reader: false
12
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
12
13
 
13
14
  # @param execution_mode [Symbol, nil] specifies how and where jobs should be executed. You can also set this with the environment variable +GOOD_JOB_EXECUTION_MODE+.
14
15
  #
@@ -29,8 +30,8 @@ module GoodJob
29
30
  GoodJob::Configuration.validate_execution_mode(@_execution_mode_override) if @_execution_mode_override
30
31
  @capsule = _capsule
31
32
 
32
- self.class.instances << self
33
33
  start_async if GoodJob.async_ready?
34
+ self.class.instances << self
34
35
  end
35
36
 
36
37
  # Enqueues the ActiveJob job to be performed.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob # :nodoc:
3
4
  # Extends an ActiveRecord odel to override the connection and use
4
5
  # an explicit connection that has been removed from the pool.
data/lib/good_job/bulk.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
4
 
4
5
  module GoodJob
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # A GoodJob::Capsule contains the resources necessary to execute jobs, including
4
5
  # a {GoodJob::Scheduler}, {GoodJob::Poller}, {GoodJob::Notifier}, and {GoodJob::CronManager}.
@@ -8,16 +9,16 @@ module GoodJob
8
9
  # @!scope class
9
10
  # List of all instantiated Capsules in the current process.
10
11
  # @return [Array<GoodJob::Capsule>, nil]
11
- cattr_reader :instances, default: [], instance_reader: false
12
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
12
13
 
13
14
  # @param configuration [GoodJob::Configuration] Configuration to use for this capsule.
14
15
  def initialize(configuration: GoodJob.configuration)
15
- self.class.instances << self
16
16
  @configuration = configuration
17
-
18
17
  @startable = true
19
18
  @running = false
20
19
  @mutex = Mutex.new
20
+
21
+ self.class.instances << self
21
22
  end
22
23
 
23
24
  # Start the capsule once. After a shutdown, {#restart} must be used to start again.
@@ -49,7 +50,7 @@ module GoodJob
49
50
  # * +nil+ will trigger a shutdown but not wait for it to complete.
50
51
  # @return [void]
51
52
  def shutdown(timeout: :default)
52
- timeout = timeout == :default ? @configuration.shutdown_timeout : timeout
53
+ timeout = @configuration.shutdown_timeout if timeout == :default
53
54
  GoodJob._shutdown_all([@notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
54
55
  @startable = false
55
56
  @running = false
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob # :nodoc:
3
4
  # Tracks thresholds for cleaning up old jobs.
4
5
  class CleanupTracker
@@ -17,7 +18,7 @@ module GoodJob # :nodoc:
17
18
  end
18
19
 
19
20
  # Increments job count.
20
- # @return [void]
21
+ # @return [Integer]
21
22
  def increment
22
23
  self.job_count += 1
23
24
  end
data/lib/good_job/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'thor'
3
4
 
4
5
  module GoodJob
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # +GoodJob::Configuration+ provides normalized configuration information to
@@ -28,6 +29,8 @@ module GoodJob
28
29
  DEFAULT_ENABLE_CRON = false
29
30
  # Default to enabling LISTEN/NOTIFY
30
31
  DEFAULT_ENABLE_LISTEN_NOTIFY = true
32
+ # Default Dashboard I18n locale
33
+ DEFAULT_DASHBOARD_DEFAULT_LOCALE = :en
31
34
 
32
35
  def self.validate_execution_mode(execution_mode)
33
36
  raise ArgumentError, "GoodJob execution mode must be one of #{EXECUTION_MODES.join(', ')}. It was '#{execution_mode}' which is not valid." unless execution_mode.in?(EXECUTION_MODES)
@@ -345,6 +348,10 @@ module GoodJob
345
348
  rails_config[:smaller_number_is_higher_priority]
346
349
  end
347
350
 
351
+ def dashboard_default_locale
352
+ rails_config[:dashboard_default_locale] || DEFAULT_DASHBOARD_DEFAULT_LOCALE
353
+ end
354
+
348
355
  private
349
356
 
350
357
  def rails_config
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "concurrent/hash"
3
4
  require "concurrent/scheduled_task"
4
5
  require "fugit"
@@ -12,7 +13,7 @@ module GoodJob # :nodoc:
12
13
  # @!scope class
13
14
  # List of all instantiated CronManagers in the current process.
14
15
  # @return [Array<GoodJob::CronManager>, nil]
15
- cattr_reader :instances, default: [], instance_reader: false
16
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
16
17
 
17
18
  # Task observer for cron task
18
19
  # @param time [Time]
@@ -35,9 +36,8 @@ module GoodJob # :nodoc:
35
36
  @cron_entries = cron_entries
36
37
  @tasks = Concurrent::Hash.new
37
38
 
38
- self.class.instances << self
39
-
40
39
  start if start_on_initialize
40
+ self.class.instances << self
41
41
  end
42
42
 
43
43
  # Schedule tasks that will enqueue jobs based on their schedule
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
4
 
4
5
  module GoodJob
@@ -11,6 +12,7 @@ module GoodJob
11
12
  cron_key
12
13
  error_on_discard
13
14
  error_on_retry
15
+ error_on_retry_stopped
14
16
  execution
15
17
  execution_interrupted
16
18
  execution_retried
@@ -40,6 +42,12 @@ module GoodJob
40
42
  # @return [Exception, nil]
41
43
  thread_mattr_accessor :error_on_retry
42
44
 
45
+ # @!attribute [rw] error_on_retry_stopped
46
+ # @!scope class
47
+ # Error captured by retry_stopped
48
+ # @return [Exception, nil]
49
+ thread_mattr_accessor :error_on_retry_stopped
50
+
43
51
  # @!attribute [rw] executions
44
52
  # @!scope class
45
53
  # Execution
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # Manages daemonization of the current process.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Ruby on Rails integration.
4
5
  class Engine < ::Rails::Engine
@@ -19,6 +20,10 @@ module GoodJob
19
20
  GoodJob::CurrentThread.error_on_retry = event.payload[:error]
20
21
  end
21
22
 
23
+ ActiveSupport::Notifications.subscribe "retry_stopped.active_job" do |event|
24
+ GoodJob::CurrentThread.error_on_retry_stopped = event.payload[:error]
25
+ end
26
+
22
27
  ActiveSupport::Notifications.subscribe "discard.active_job" do |event|
23
28
  GoodJob::CurrentThread.error_on_discard = event.payload[:error]
24
29
  end
@@ -35,6 +40,14 @@ module GoodJob
35
40
  end
36
41
  end
37
42
 
43
+ initializer 'good_job.active_record' do
44
+ config.to_prepare do
45
+ ActiveSupport.on_load :good_job_base_record, run_once: true do
46
+ GoodJob::BaseRecord.class_eval(&GoodJob._active_record_configuration) if GoodJob._active_record_configuration
47
+ end
48
+ end
49
+ end
50
+
38
51
  initializer "good_job.start_async" do
39
52
  # This hooks into the hookable places during Rails boot, which is unfortunately not Rails.application.initialized?
40
53
  # If an Adapter is initialized during boot, we want to want to start async executors once the framework dependencies have loaded.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Exception raised when a job is interrupted by a SIGKILL or power failure.
4
5
  class InterruptError < StandardError
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'concurrent/delay'
3
4
 
4
5
  module GoodJob
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  #
4
5
  # Listens to GoodJob notifications and logs them.
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodJob # :nodoc:
4
+ # Metrics for the scheduler.
5
+ class Metrics
6
+ def initialize
7
+ @empty_executions = Concurrent::AtomicFixnum.new
8
+ @errored_executions = Concurrent::AtomicFixnum.new
9
+ @succeeded_executions = Concurrent::AtomicFixnum.new
10
+ @unexecutable_executions = Concurrent::AtomicFixnum.new
11
+ end
12
+
13
+ # Increments number of empty queried executions.
14
+ # @return [Integer]
15
+ def increment_empty_executions
16
+ @empty_executions.increment
17
+ end
18
+
19
+ # Increments number of failed executions.
20
+ # @return [Integer]
21
+ def increment_errored_executions
22
+ @errored_executions.increment
23
+ end
24
+
25
+ # Increments number of succeeded executions.
26
+ # @return [Integer]
27
+ def increment_succeeded_executions
28
+ @succeeded_executions.increment
29
+ end
30
+
31
+ # Increments number of unlocked executions.
32
+ # @return [Integer]
33
+ def increment_unexecutable_executions
34
+ @unexecutable_executions.increment
35
+ end
36
+
37
+ def to_h
38
+ {
39
+ empty_executions_count: @empty_executions.value,
40
+ errored_executions_count: @errored_executions.value,
41
+ succeeded_executions_count: @succeeded_executions.value,
42
+ unexecutable_executions_count: @unexecutable_executions.value,
43
+ }.tap do |values|
44
+ values[:total_executions_count] = values.values.sum
45
+ end
46
+ end
47
+
48
+ # Reset counters.
49
+ # @return [void]
50
+ def reset
51
+ @empty_executions.value = 0
52
+ @errored_executions.value = 0
53
+ @succeeded_executions.value = 0
54
+ @unexecutable_executions.value = 0
55
+ end
56
+ end
57
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GoodJob
3
4
  # Delegates the interface of a single {Scheduler} to multiple Schedulers.
4
5
  class MultiScheduler
@@ -3,11 +3,12 @@
3
3
  module GoodJob # :nodoc:
4
4
  class Notifier # :nodoc:
5
5
  # Extends the Notifier to register the process in the database.
6
- module ProcessRegistration
6
+ module ProcessHeartbeat
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
10
  set_callback :listen, :after, :register_process
11
+ set_callback :tick, :before, :refresh_process
11
12
  set_callback :unlisten, :after, :deregister_process
12
13
  end
13
14
 
@@ -19,6 +20,14 @@ module GoodJob # :nodoc:
19
20
  end
20
21
  end
21
22
 
23
+ def refresh_process
24
+ GoodJob::Process.with_connection(connection) do
25
+ Process.logger.silence do
26
+ @process&.refresh_if_stale(cleanup: true)
27
+ end
28
+ end
29
+ end
30
+
22
31
  # Deregisters the current process.
23
32
  def deregister_process
24
33
  GoodJob::Process.with_connection(connection) do
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'active_support/core_ext/module/attribute_accessors_per_thread'
3
4
  require 'concurrent/atomic/atomic_boolean'
4
- require "good_job/notifier/process_registration"
5
+ require "good_job/notifier/process_heartbeat"
5
6
 
6
7
  module GoodJob # :nodoc:
7
8
  #
@@ -13,9 +14,9 @@ module GoodJob # :nodoc:
13
14
  #
14
15
  class Notifier
15
16
  include ActiveSupport::Callbacks
16
- define_callbacks :listen, :unlisten
17
+ define_callbacks :listen, :tick, :unlisten
17
18
 
18
- include Notifier::ProcessRegistration
19
+ include ProcessHeartbeat
19
20
 
20
21
  # Default Postgres channel for LISTEN/NOTIFY
21
22
  CHANNEL = 'good_job'
@@ -46,7 +47,7 @@ module GoodJob # :nodoc:
46
47
  # @!scope class
47
48
  # List of all instantiated Notifiers in the current process.
48
49
  # @return [Array<GoodJob::Notifier>, nil]
49
- cattr_reader :instances, default: [], instance_reader: false
50
+ cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
50
51
 
51
52
  # @!attribute [rw] connection
52
53
  # @!scope class
@@ -77,10 +78,9 @@ module GoodJob # :nodoc:
77
78
  @connection_errors_reported = Concurrent::AtomicBoolean.new(false)
78
79
  @enable_listening = enable_listening
79
80
 
80
- self.class.instances << self
81
-
82
81
  create_executor
83
82
  listen
83
+ self.class.instances << self
84
84
  end
85
85
 
86
86
  # Tests whether the notifier is active and has acquired a dedicated database connection.
@@ -189,18 +189,20 @@ module GoodJob # :nodoc:
189
189
  end
190
190
 
191
191
  while thr_executor.running?
192
- wait_for_notify do |channel, payload|
193
- next unless channel == CHANNEL
194
-
195
- ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
196
- parsed_payload = JSON.parse(payload, symbolize_names: true)
197
- thr_recipients.each do |recipient|
198
- target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
199
- target.send(method_name, parsed_payload)
192
+ run_callbacks :tick do
193
+ wait_for_notify do |channel, payload|
194
+ next unless channel == CHANNEL
195
+
196
+ ActiveSupport::Notifications.instrument("notifier_notified.good_job", { payload: payload })
197
+ parsed_payload = JSON.parse(payload, symbolize_names: true)
198
+ thr_recipients.each do |recipient|
199
+ target, method_name = recipient.is_a?(Array) ? recipient : [recipient, :call]
200
+ target.send(method_name, parsed_payload)
201
+ end
200
202
  end
201
- end
202
203
 
203
- reset_connection_errors
204
+ reset_connection_errors
205
+ end
204
206
  end
205
207
  end
206
208
  ensure