good_job 3.21.0 → 3.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1e8a8a3ec28224d48f23c9c2795cd15f9eb515ba9059ab2bea4316a03b42aa9
4
- data.tar.gz: e469a0f1be765789433ef9d0096c6c1c3fe3e87c90a184c11996a204a36dabd1
3
+ metadata.gz: 4f34be14a5677efd6647698f22bcf5189528ca574f34ee5361647f79a02cbfbd
4
+ data.tar.gz: 575a2d5b98556a810b50a31192b3dde8cfd3ab8e9cf09ef57c6efc245e4dc425
5
5
  SHA512:
6
- metadata.gz: 6546caba43d15714d87c2b6f3a072b493804685411f7fdd3c73b8aaa0aacffb2f77ae8fcbb8f3eb0d9aeed9fbfab2c4175b49adebd3d5356a8994138b92e3852
7
- data.tar.gz: 30f85524258b9f1b2b774601ef0fd23965ed21eac87b0a3c0491a94c0fe85a3c70efb57b9979d11047d8ea5176cf0008317c860c27411db86ecdf8ef2fd0a69c
6
+ metadata.gz: 235c2d8c5519f6af13b5e98586b0e32f1436cf969ce35d8549cecb4e6e9ad172a01a2914e8da1928b37f87b1c47a9317ec3a66dd92412903e5808f308eb67753
7
+ data.tar.gz: b0c1f3ee48467b924dcde22d0529a8728873babf0202c531c615b3c3e5a64a201f094d77b9414cf532cb90e1d8610ebf07176a3e7688ca6639bf71ad52be2592
data/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## [v3.21.1](https://github.com/bensheldon/good_job/tree/v3.21.1) (2023-11-14)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.21.0...v3.21.1)
6
+
7
+ **Fixed bugs:**
8
+
9
+ - Explicitly require `active_job/arguments` in `GoodJob::BatchRecord` [\#1150](https://github.com/bensheldon/good_job/pull/1150) ([hidenba](https://github.com/hidenba))
10
+ - Bug: Polling only activates single thread, should eagerly create additional threads when jobs exist [\#1148](https://github.com/bensheldon/good_job/pull/1148) ([bensheldon](https://github.com/bensheldon))
11
+
12
+ **Closed issues:**
13
+
14
+ - Error when executing ActiveJob::Batch.new in Rails 7.1.x [\#1149](https://github.com/bensheldon/good_job/issues/1149)
15
+ - Show whether or not cron scheduler is enable in dashboard \(UI\) [\#1117](https://github.com/bensheldon/good_job/issues/1117)
16
+ - ActiveRecord::ConnectionNotEstablished For rails multi DB [\#1103](https://github.com/bensheldon/good_job/issues/1103)
17
+ - Rails API dies when using latest good\_job version [\#952](https://github.com/bensheldon/good_job/issues/952)
18
+ - config.good\_job.preserve\_job\_records = false not working with CRON [\#927](https://github.com/bensheldon/good_job/issues/927)
19
+ - Pundit::NotDefinedError \(unable to find policy `Admin::GoodJob::JobPolicy` for \) - version 2.13.0 and above [\#618](https://github.com/bensheldon/good_job/issues/618)
20
+ - Running CLI under foreman doesn't display log output until exit [\#490](https://github.com/bensheldon/good_job/issues/490)
21
+
22
+ **Merged pull requests:**
23
+
24
+ - Always instantiate MultiScheduler; delegate Scheduler Metrics to JobPerformer [\#1147](https://github.com/bensheldon/good_job/pull/1147) ([bensheldon](https://github.com/bensheldon))
25
+ - Clarify on concurrency uniqueness constraints [\#1144](https://github.com/bensheldon/good_job/pull/1144) ([Earlopain](https://github.com/Earlopain))
26
+
3
27
  ## [v3.21.0](https://github.com/bensheldon/good_job/tree/v3.21.0) (2023-11-06)
4
28
 
5
29
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v3.20.0...v3.21.0)
data/README.md CHANGED
@@ -470,7 +470,7 @@ class MyJob < ApplicationJob
470
470
  # Can be String or Lambda/Proc that is invoked in the context of the job.
471
471
  # Note: Arguments passed to #perform_later can be accessed through ActiveJob's `arguments` method
472
472
  # which is an array containing positional arguments and, optionally, a kwarg hash.
473
- key: -> { "Unique-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "Unique-Alice-v2"
473
+ key: -> { "MyJob-#{arguments.first}-#{arguments.last[:version]}" } # MyJob.perform_later("Alice", version: 'v2') => "MyJob-Alice-v2"
474
474
  )
475
475
 
476
476
  def perform(first_name)
@@ -483,7 +483,7 @@ When testing, the resulting concurrency key value can be inspected:
483
483
 
484
484
  ```ruby
485
485
  job = MyJob.perform_later("Alice")
486
- job.good_job_concurrency_key #=> "Unique-Alice"
486
+ job.good_job_concurrency_key #=> "MyJob-Alice"
487
487
  ```
488
488
 
489
489
  #### How concurrency controls work
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_job/arguments'
4
+
3
5
  module GoodJob
4
6
  class BatchRecord < BaseRecord
5
7
  include AdvisoryLockable
@@ -248,6 +248,7 @@ module GoodJob
248
248
 
249
249
  # Finds the next eligible Execution, acquire an advisory lock related to it, and
250
250
  # executes the job.
251
+ # @yield [Execution, nil] The next eligible Execution, or +nil+ if none found, before it is performed.
251
252
  # @return [ExecutionResult, nil]
252
253
  # If a job was executed, returns an array with the {Execution} record, the
253
254
  # return value for the job's +#perform+ method, and the exception the job
@@ -256,21 +257,19 @@ module GoodJob
256
257
  def self.perform_with_advisory_lock(parsed_queues: nil, queue_select_limit: nil)
257
258
  execution = nil
258
259
  result = nil
260
+
259
261
  unfinished.dequeueing_ordered(parsed_queues).only_scheduled.limit(1).with_advisory_lock(select_limit: queue_select_limit) do |executions|
260
262
  execution = executions.first
261
- break if execution.blank?
262
-
263
- unless execution.executable?
264
- result = ExecutionResult.new(value: nil, unexecutable: true)
263
+ if execution&.executable?
264
+ yield(execution) if block_given?
265
+ result = execution.perform
266
+ else
265
267
  execution = nil
266
- break
268
+ yield(nil) if block_given?
267
269
  end
268
-
269
- yield(execution) if block_given?
270
- result = execution.perform
271
270
  end
272
- execution&.run_callbacks(:perform_unlocked)
273
271
 
272
+ execution&.run_callbacks(:perform_unlocked)
274
273
  result
275
274
  end
276
275
 
@@ -52,6 +52,10 @@ module GoodJob # :nodoc:
52
52
  end
53
53
 
54
54
  def self.ns_current_state
55
+ total_succeeded_executions_count = GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:succeeded_executions_count, 0) }
56
+ total_errored_executions_count = GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:errored_executions_count, 0) }
57
+ total_empty_executions_count = GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:empty_executions_count, 0) }
58
+
55
59
  {
56
60
  id: ns_current_id,
57
61
  hostname: Socket.gethostname,
@@ -61,8 +65,10 @@ module GoodJob # :nodoc:
61
65
  retry_on_unhandled_error: GoodJob.retry_on_unhandled_error,
62
66
  schedulers: GoodJob::Scheduler.instances.map(&:stats),
63
67
  cron_enabled: GoodJob.configuration.enable_cron?,
64
- total_succeeded_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:succeeded_executions_count) },
65
- total_errored_executions_count: GoodJob::Scheduler.instances.sum { |scheduler| scheduler.stats.fetch(:errored_executions_count) },
68
+ total_succeeded_executions_count: total_succeeded_executions_count,
69
+ total_errored_executions_count: total_errored_executions_count,
70
+ total_executions_count: total_succeeded_executions_count + total_errored_executions_count,
71
+ total_empty_executions_count: total_empty_executions_count,
66
72
  database_connection_pool: {
67
73
  size: connection_pool.size,
68
74
  active: connection_pool.connections.count(&:in_use?),
@@ -32,9 +32,9 @@ module GoodJob
32
32
  @shared_executor = GoodJob::SharedExecutor.new
33
33
  @notifier = GoodJob::Notifier.new(enable_listening: @configuration.enable_listen_notify, executor: @shared_executor.executor)
34
34
  @poller = GoodJob::Poller.new(poll_interval: @configuration.poll_interval)
35
- @scheduler = GoodJob::Scheduler.from_configuration(@configuration, warm_cache_on_initialize: true)
36
- @notifier.recipients << [@scheduler, :create_thread]
37
- @poller.recipients << [@scheduler, :create_thread]
35
+ @multi_scheduler = GoodJob::MultiScheduler.from_configuration(@configuration, warm_cache_on_initialize: true)
36
+ @notifier.recipients.push([@multi_scheduler, :create_thread])
37
+ @poller.recipients.push(-> { @multi_scheduler.create_thread({ fanout: true }) })
38
38
 
39
39
  @cron_manager = GoodJob::CronManager.new(@configuration.cron_entries, start_on_initialize: true, executor: @shared_executor.executor) if @configuration.enable_cron?
40
40
 
@@ -52,7 +52,7 @@ module GoodJob
52
52
  # @return [void]
53
53
  def shutdown(timeout: NONE)
54
54
  timeout = @configuration.shutdown_timeout if timeout == NONE
55
- GoodJob._shutdown_all([@shared_executor, @notifier, @poller, @scheduler, @cron_manager].compact, timeout: timeout)
55
+ GoodJob._shutdown_all([@shared_executor, @notifier, @poller, @multi_scheduler, @cron_manager].compact, timeout: timeout)
56
56
  @startable = false
57
57
  @running = false
58
58
  end
@@ -74,7 +74,7 @@ module GoodJob
74
74
 
75
75
  # @return [Boolean] Whether the capsule has been shutdown.
76
76
  def shutdown?
77
- [@shared_executor, @notifier, @poller, @scheduler, @cron_manager].compact.all?(&:shutdown?)
77
+ [@shared_executor, @notifier, @poller, @multi_scheduler, @cron_manager].compact.all?(&:shutdown?)
78
78
  end
79
79
 
80
80
  # Creates an execution thread(s) with the given attributes.
@@ -82,7 +82,7 @@ module GoodJob
82
82
  # @return [Boolean, nil] Whether the thread was created.
83
83
  def create_thread(job_state = nil)
84
84
  start if startable?
85
- @scheduler&.create_thread(job_state)
85
+ @multi_scheduler&.create_thread(job_state)
86
86
  end
87
87
 
88
88
  private
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/atomic/atomic_fixnum'
4
+
5
+ module GoodJob # :nodoc:
6
+ class JobPerformer
7
+ # Metrics for the scheduler.
8
+ class Metrics
9
+ def initialize
10
+ @mutex = Mutex.new
11
+ @empty_executions = Concurrent::AtomicFixnum.new
12
+ @errored_executions = Concurrent::AtomicFixnum.new
13
+ @succeeded_executions = Concurrent::AtomicFixnum.new
14
+ @execution_at = nil
15
+ @check_queue_at = nil
16
+ end
17
+
18
+ # Increments number of failed executions.
19
+ # @return [Integer]
20
+ def increment_errored_executions
21
+ @execution_at = Time.current
22
+ @errored_executions.increment
23
+ end
24
+
25
+ # Increments number of succeeded executions.
26
+ # @return [Integer]
27
+ def increment_succeeded_executions
28
+ @execution_at = Time.current
29
+ @succeeded_executions.increment
30
+ end
31
+
32
+ # Increments number of dequeue attempts with no executions.
33
+ # @return [Integer]
34
+ def increment_empty_executions
35
+ @execution_at = Time.current
36
+ @empty_executions.increment
37
+ end
38
+
39
+ # Last time a job was executed (started or finished).
40
+ # @return [Time, nil]
41
+ def touch_execution_at
42
+ @execution_at = Time.current
43
+ end
44
+
45
+ # Last time the queue was checked for jobs.
46
+ # @return [Time, nil]
47
+ def touch_check_queue_at
48
+ @check_queue_at = Time.current
49
+ end
50
+
51
+ # All metrics in a Hash.
52
+ # @return [Hash]
53
+ def to_h
54
+ {
55
+ empty_executions_count: @empty_executions.value,
56
+ errored_executions_count: @errored_executions.value,
57
+ succeeded_executions_count: @succeeded_executions.value,
58
+ }.tap do |values|
59
+ values[:total_executions_count] = values[:succeeded_executions_count] + values[:errored_executions_count]
60
+ values[:execution_at] = @execution_at
61
+ values[:check_queue_at] = @check_queue_at
62
+ end
63
+ end
64
+
65
+ # Reset counters.
66
+ # @return [void]
67
+ def reset
68
+ @empty_executions.value = 0
69
+ @errored_executions.value = 0
70
+ @succeeded_executions.value = 0
71
+ @execution_at = nil
72
+ @check_queue_at = nil
73
+ end
74
+ end
75
+ end
76
+ end
@@ -16,6 +16,7 @@ module GoodJob
16
16
  # @param queue_string [String] Queues to execute jobs from
17
17
  def initialize(queue_string)
18
18
  @queue_string = queue_string
19
+ @metrics = Metrics.new
19
20
  end
20
21
 
21
22
  # A meaningful name to identify the performer in logs and for debugging.
@@ -25,15 +26,28 @@ module GoodJob
25
26
  end
26
27
 
27
28
  # Perform the next eligible job
29
+ # @yield [Execution] Yields the execution, if one is dequeued
28
30
  # @return [Object, nil] Returns job result or +nil+ if no job was found
29
31
  def next
30
32
  active_job_id = nil
31
33
  job_query.perform_with_advisory_lock(parsed_queues: parsed_queues, queue_select_limit: GoodJob.configuration.queue_select_limit) do |execution|
32
- active_job_id = execution.active_job_id
33
- performing_active_job_ids << active_job_id
34
+ @metrics.touch_check_queue_at
35
+
36
+ if execution
37
+ active_job_id = execution.active_job_id
38
+ performing_active_job_ids << active_job_id
39
+ @metrics.touch_execution_at
40
+ yield(execution) if block_given?
41
+ else
42
+ @metrics.increment_empty_executions
43
+ end
44
+ end.tap do |result|
45
+ if result
46
+ result.succeeded? ? @metrics.increment_succeeded_executions : @metrics.increment_errored_executions
47
+ end
34
48
  end
35
49
  ensure
36
- performing_active_job_ids.delete(active_job_id)
50
+ performing_active_job_ids.delete(active_job_id) if active_job_id
37
51
  end
38
52
 
39
53
  # Tests whether this performer should be used in GoodJob's current state.
@@ -72,6 +86,20 @@ module GoodJob
72
86
  GoodJob.cleanup_preserved_jobs
73
87
  end
74
88
 
89
+ # Metrics about this performer
90
+ # @return [Hash]
91
+ def stats
92
+ {
93
+ name: name,
94
+ }.merge(@metrics.to_h)
95
+ end
96
+
97
+ # Reset metrics about this performer
98
+ # @return [void]
99
+ def reset_stats
100
+ @metrics.reset
101
+ end
102
+
75
103
  private
76
104
 
77
105
  attr_reader :queue_string
@@ -3,6 +3,29 @@
3
3
  module GoodJob
4
4
  # Delegates the interface of a single {Scheduler} to multiple Schedulers.
5
5
  class MultiScheduler
6
+ # Creates MultiScheduler from a GoodJob::Configuration instance.
7
+ # @param configuration [GoodJob::Configuration]
8
+ # @param warm_cache_on_initialize [Boolean]
9
+ # @return [GoodJob::MultiScheduler]
10
+ def self.from_configuration(configuration, warm_cache_on_initialize: false)
11
+ schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
12
+ queue_string, max_threads = queue_string_and_max_threads.split(':')
13
+ max_threads = (max_threads || configuration.max_threads).to_i
14
+
15
+ job_performer = GoodJob::JobPerformer.new(queue_string)
16
+ GoodJob::Scheduler.new(
17
+ job_performer,
18
+ max_threads: max_threads,
19
+ max_cache: configuration.max_cache,
20
+ warm_cache_on_initialize: warm_cache_on_initialize,
21
+ cleanup_interval_seconds: configuration.cleanup_interval_seconds,
22
+ cleanup_interval_jobs: configuration.cleanup_interval_jobs
23
+ )
24
+ end
25
+
26
+ new(schedulers)
27
+ end
28
+
6
29
  # @return [Array<Scheduler>] List of the scheduler delegates
7
30
  attr_reader :schedulers
8
31
 
@@ -43,7 +66,7 @@ module GoodJob
43
66
  def create_thread(state = nil)
44
67
  results = []
45
68
 
46
- if state
69
+ if state && !state[:fanout]
47
70
  schedulers.any? do |scheduler|
48
71
  scheduler.create_thread(state).tap { |result| results << result }
49
72
  end
@@ -61,5 +84,19 @@ module GoodJob
61
84
  nil
62
85
  end
63
86
  end
87
+
88
+ def stats
89
+ scheduler_stats = schedulers.map(&:stats)
90
+
91
+ {
92
+ schedulers: scheduler_stats,
93
+ empty_executions_count: scheduler_stats.sum { |stats| stats.fetch(:empty_executions_count, 0) },
94
+ errored_executions_count: scheduler_stats.sum { |stats| stats.fetch(:errored_executions_count, 0) },
95
+ succeeded_executions_count: scheduler_stats.sum { |stats| stats.fetch(:succeeded_executions_count, 0) },
96
+ total_executions_count: scheduler_stats.sum { |stats| stats.fetch(:total_executions_count, 0) },
97
+ execution_at: scheduler_stats.map { |stats| stats.fetch(:execution_at, nil) }.compact.max,
98
+ check_queue_at: scheduler_stats.map { |stats| stats.fetch(:check_queue_at, nil) }.compact.max,
99
+ }
100
+ end
64
101
  end
65
102
  end
@@ -4,7 +4,6 @@ require "concurrent/executor/thread_pool_executor"
4
4
  require "concurrent/executor/timer_set"
5
5
  require "concurrent/scheduled_task"
6
6
  require "concurrent/utility/processor_counter"
7
- require 'good_job/metrics'
8
7
 
9
8
  module GoodJob # :nodoc:
10
9
  #
@@ -36,33 +35,6 @@ module GoodJob # :nodoc:
36
35
  # @return [Array<GoodJob::Scheduler>, nil]
37
36
  cattr_reader :instances, default: Concurrent::Array.new, instance_reader: false
38
37
 
39
- # Creates GoodJob::Scheduler(s) and Performers from a GoodJob::Configuration instance.
40
- # @param configuration [GoodJob::Configuration]
41
- # @param warm_cache_on_initialize [Boolean]
42
- # @return [GoodJob::Scheduler, GoodJob::MultiScheduler]
43
- def self.from_configuration(configuration, warm_cache_on_initialize: false)
44
- schedulers = configuration.queue_string.split(';').map do |queue_string_and_max_threads|
45
- queue_string, max_threads = queue_string_and_max_threads.split(':')
46
- max_threads = (max_threads || configuration.max_threads).to_i
47
-
48
- job_performer = GoodJob::JobPerformer.new(queue_string)
49
- GoodJob::Scheduler.new(
50
- job_performer,
51
- max_threads: max_threads,
52
- max_cache: configuration.max_cache,
53
- warm_cache_on_initialize: warm_cache_on_initialize,
54
- cleanup_interval_seconds: configuration.cleanup_interval_seconds,
55
- cleanup_interval_jobs: configuration.cleanup_interval_jobs
56
- )
57
- end
58
-
59
- if schedulers.size > 1
60
- GoodJob::MultiScheduler.new(schedulers)
61
- else
62
- schedulers.first
63
- end
64
- end
65
-
66
38
  # Human readable name of the scheduler that includes configuration values.
67
39
  # @return [String]
68
40
  attr_reader :name
@@ -88,7 +60,6 @@ module GoodJob # :nodoc:
88
60
  @executor_options[:name] = name
89
61
 
90
62
  @cleanup_tracker = CleanupTracker.new(cleanup_interval_seconds: cleanup_interval_seconds, cleanup_interval_jobs: cleanup_interval_jobs)
91
- @metrics = ::GoodJob::Metrics.new
92
63
  @executor_options[:name] = name
93
64
 
94
65
  create_executor
@@ -143,7 +114,7 @@ module GoodJob # :nodoc:
143
114
 
144
115
  instrument("scheduler_restart_pools") do
145
116
  shutdown(timeout: timeout)
146
- @metrics.reset
117
+ @performer.reset_stats
147
118
  create_executor
148
119
  warm_cache
149
120
  end
@@ -152,16 +123,17 @@ module GoodJob # :nodoc:
152
123
  # Wakes a thread to allow the performer to execute a task.
153
124
  # @param state [Hash, nil] Contextual information for the performer. See {JobPerformer#next?}.
154
125
  # @return [Boolean, nil] Whether work was started.
155
- #
156
126
  # * +nil+ if the scheduler is unable to take new work, for example if the thread pool is shut down or at capacity.
157
127
  # * +true+ if the performer started executing work.
158
128
  # * +false+ if the performer decides not to attempt to execute a task based on the +state+ that is passed to it.
159
129
  def create_thread(state = nil)
160
130
  return nil unless executor.running?
161
131
 
162
- if state
132
+ if state.present?
163
133
  return false unless performer.next?(state)
164
134
 
135
+ fanout = state&.fetch(:fanout, nil)
136
+
165
137
  if state[:count]
166
138
  # When given state for multiple jobs, try to create a thread for each one.
167
139
  # Return true if a thread can be created for all of them, nil if partial or none.
@@ -193,7 +165,7 @@ module GoodJob # :nodoc:
193
165
  return nil unless remaining_cache_count.positive?
194
166
  end
195
167
 
196
- create_task(delay)
168
+ create_task(delay, fanout: fanout)
197
169
 
198
170
  run_now ? true : nil
199
171
  end
@@ -207,16 +179,6 @@ module GoodJob # :nodoc:
207
179
  unhandled_error = thread_error || result&.unhandled_error
208
180
  GoodJob._on_thread_error(unhandled_error) if unhandled_error
209
181
 
210
- if unhandled_error || result&.handled_error
211
- @metrics.increment_errored_executions
212
- elsif result&.unexecutable
213
- @metrics.increment_unexecutable_executions
214
- elsif result
215
- @metrics.increment_succeeded_executions
216
- else
217
- @metrics.increment_empty_executions
218
- end
219
-
220
182
  instrument("finished_job_task", { result: output, error: thread_error, time: time })
221
183
  return unless output
222
184
 
@@ -232,6 +194,7 @@ module GoodJob # :nodoc:
232
194
  # @return [Hash]
233
195
  def stats
234
196
  available_threads = executor.ready_worker_count
197
+
235
198
  {
236
199
  name: name,
237
200
  queues: performer.name,
@@ -241,7 +204,7 @@ module GoodJob # :nodoc:
241
204
  max_cache: @max_cache,
242
205
  active_cache: cache_count,
243
206
  available_cache: remaining_cache_count,
244
- }.merge!(@metrics.to_h)
207
+ }.merge!(@performer.stats.without(:name))
245
208
  end
246
209
 
247
210
  # Preload existing runnable and future-scheduled jobs
@@ -300,12 +263,15 @@ module GoodJob # :nodoc:
300
263
  end
301
264
 
302
265
  # @param delay [Integer]
266
+ # @param fanout [Boolean] Whether to eagerly create a 2nd execution thread if a job is found.
303
267
  # @return [void]
304
- def create_task(delay = 0)
305
- future = Concurrent::ScheduledTask.new(delay, args: [performer], executor: executor, timer_set: timer_set) do |thr_performer|
268
+ def create_task(delay = 0, fanout: false)
269
+ future = Concurrent::ScheduledTask.new(delay, args: [self, performer], executor: executor, timer_set: timer_set) do |thr_scheduler, thr_performer|
306
270
  Thread.current.name = Thread.current.name.sub("-worker-", "-thread-") if Thread.current.name
307
271
  Rails.application.reloader.wrap do
308
- thr_performer.next
272
+ thr_performer.next do |found|
273
+ thr_scheduler.create_thread({ fanout: fanout }) if found && fanout
274
+ end
309
275
  end
310
276
  end
311
277
  future.add_observer(self, :task_observer)
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '3.21.0'
5
+ VERSION = '3.21.1'
6
6
 
7
7
  # GoodJob version as Gem::Version object
8
8
  GEM_VERSION = Gem::Version.new(VERSION)
data/lib/good_job.rb CHANGED
@@ -26,6 +26,7 @@ require 'good_job/current_thread'
26
26
  require "good_job/daemon"
27
27
  require "good_job/dependencies"
28
28
  require "good_job/job_performer"
29
+ require "good_job/job_performer/metrics"
29
30
  require "good_job/log_subscriber"
30
31
  require "good_job/multi_scheduler"
31
32
  require "good_job/notifier"
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.21.0
4
+ version: 3.21.1
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-06 00:00:00.000000000 Z
11
+ date: 2023-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -366,8 +366,8 @@ files:
366
366
  - lib/good_job/http_server.rb
367
367
  - lib/good_job/interrupt_error.rb
368
368
  - lib/good_job/job_performer.rb
369
+ - lib/good_job/job_performer/metrics.rb
369
370
  - lib/good_job/log_subscriber.rb
370
- - lib/good_job/metrics.rb
371
371
  - lib/good_job/multi_scheduler.rb
372
372
  - lib/good_job/notifier.rb
373
373
  - lib/good_job/notifier/process_heartbeat.rb
@@ -1,57 +0,0 @@
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