good_job 3.21.0 → 3.21.1

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