good_job 4.5.0 → 4.6.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: 3f0a8926625f33b36c56def63a1507cfb0f8981a876449e907396aa2338b0b78
4
- data.tar.gz: ad45bd8133d5f68bcf190bac3f7656864e10df2e49be477024e9f0c8422ce222
3
+ metadata.gz: 4e6676f189c0e22c751837d8792b84ece28dfcc118941077abfcdfac7e352c18
4
+ data.tar.gz: 891e630829ec28fb022d516e50d5d9c61e51dc81531ce3f20c25f2facfee09c6
5
5
  SHA512:
6
- metadata.gz: 2f2e5c4ac4020dfd7aaed53bfb9b7e7480ca91e0aa7cfcbc385ac60d91a4b08e40397734993ffdffc9d5121f40afab04540d363fef925bf8a30bf987938514ab
7
- data.tar.gz: 3460da22048d4fad135e12644c043723b2dcba8137a69a3b5bebf3b5df11e1c1a0ed0f94a8924b64cab4d1ce8b76bd55e6090a25c43f23afde41b27d398cf59a
6
+ metadata.gz: d443d783208de5f01cd003773943199c618f397a46a1ecb5d7e9167a9eaedc0511f9e4b3cec6122c17cd0be328b490230727024041b7622ad1f9f8afcaf817e1
7
+ data.tar.gz: fc1171540d6227d0e7757a09904b617791289daeec89c3045251b46e8d82959b9df4b5ecd0b4c812a2804ac7c7626712ebea93d3718984fb08a1592c097db068
data/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## [v4.6.0](https://github.com/bensheldon/good_job/tree/v4.6.0) (2024-12-12)
4
+
5
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.5.1...v4.6.0)
6
+
7
+ **Implemented enhancements:**
8
+
9
+ - Set job execution thread priority to `-3` when in async mode [\#1560](https://github.com/bensheldon/good_job/pull/1560) ([bensheldon](https://github.com/bensheldon))
10
+
11
+ **Closed issues:**
12
+
13
+ - Attaching metadata to jobs [\#1558](https://github.com/bensheldon/good_job/issues/1558)
14
+ - Lower Ruby Thread priority for jobs by default when running in Async mode [\#1554](https://github.com/bensheldon/good_job/issues/1554)
15
+ - NoMethodError: undefined method `\<' for nil \(process.rb:125 in stale?\) [\#1363](https://github.com/bensheldon/good_job/issues/1363)
16
+ - Install PgHero on the Demo app [\#1166](https://github.com/bensheldon/good_job/issues/1166)
17
+
18
+ **Merged pull requests:**
19
+
20
+ - Bump rails-html-sanitizer from 1.6.0 to 1.6.1 [\#1557](https://github.com/bensheldon/good_job/pull/1557) ([dependabot[bot]](https://github.com/apps/dependabot))
21
+ - Add PGHero to the demo app [\#1294](https://github.com/bensheldon/good_job/pull/1294) ([mec](https://github.com/mec))
22
+
23
+ ## [v4.5.1](https://github.com/bensheldon/good_job/tree/v4.5.1) (2024-11-29)
24
+
25
+ [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.5.0...v4.5.1)
26
+
27
+ **Implemented enhancements:**
28
+
29
+ - GoodJob.cleanup\_preserved\_jobs: add :include\_discarded option [\#1550](https://github.com/bensheldon/good_job/pull/1550) ([jonleighton](https://github.com/jonleighton))
30
+
31
+ **Fixed bugs:**
32
+
33
+ - Fix compatibility with `rails-head` when duplicated advisory lockable column [\#1553](https://github.com/bensheldon/good_job/pull/1553) ([Earlopain](https://github.com/Earlopain))
34
+
35
+ **Closed issues:**
36
+
37
+ - `PG::AmbiguousColumn` after upgrade to 4.5.0 [\#1551](https://github.com/bensheldon/good_job/issues/1551)
38
+
39
+ **Merged pull requests:**
40
+
41
+ - Remove usage of COALESCE from dashboard chart [\#1306](https://github.com/bensheldon/good_job/pull/1306) ([bananatron](https://github.com/bananatron))
42
+
3
43
  ## [v4.5.0](https://github.com/bensheldon/good_job/tree/v4.5.0) (2024-11-22)
4
44
 
5
45
  [Full Changelog](https://github.com/bensheldon/good_job/compare/v4.4.2...v4.5.0)
@@ -5,7 +5,7 @@ module GoodJob
5
5
  def data
6
6
  table_name = GoodJob::Execution.table_name
7
7
 
8
- sum_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
8
+ sum_query = <<~SQL.squish
9
9
  SELECT *
10
10
  FROM generate_series(
11
11
  date_trunc('hour', $1::timestamp),
@@ -21,7 +21,7 @@ module GoodJob
21
21
  table_name = GoodJob::Execution.table_name
22
22
 
23
23
  interval_entries = BUCKET_INTERVALS.map { "interval '#{_1}'" }.join(",")
24
- sum_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
24
+ sum_query = <<~SQL.squish
25
25
  SELECT
26
26
  WIDTH_BUCKET(duration, ARRAY[#{interval_entries}]) as bucket_index,
27
27
  COUNT(WIDTH_BUCKET(duration, ARRAY[#{interval_entries}])) AS count
@@ -8,9 +8,7 @@ module GoodJob
8
8
  end
9
9
 
10
10
  def data
11
- table_name = GoodJob::Job.table_name
12
-
13
- count_query = Arel.sql(GoodJob::Job.pg_or_jdbc_query(<<~SQL.squish))
11
+ count_query = <<~SQL.squish
14
12
  SELECT *
15
13
  FROM generate_series(
16
14
  date_trunc('hour', $1::timestamp),
@@ -23,7 +21,7 @@ module GoodJob
23
21
  queue_name,
24
22
  count(*) AS count
25
23
  FROM (
26
- #{@filter.filtered_query.except(:select, :order).select('queue_name', "COALESCE(#{table_name}.scheduled_at, #{table_name}.created_at)::timestamp AS scheduled_at").to_sql}
24
+ #{@filter.filtered_query.except(:select, :order).select(:queue_name, :scheduled_at).to_sql}
27
25
  ) sources
28
26
  GROUP BY date_trunc('hour', scheduled_at), queue_name
29
27
  ) sources ON sources.scheduled_at = timestamp
@@ -41,8 +41,16 @@ module GoodJob
41
41
  scope :advisory_lock, (lambda do |column: _advisory_lockable_column, function: advisory_lockable_function, select_limit: nil|
42
42
  original_query = self
43
43
 
44
+ primary_key_for_select = primary_key.to_sym
45
+ column_for_select = column.to_sym
46
+
44
47
  cte_table = Arel::Table.new(:rows)
45
- cte_query = original_query.select(primary_key, column).except(:limit)
48
+ cte_query = original_query.except(:limit)
49
+ cte_query = if primary_key_for_select == column_for_select
50
+ cte_query.select(primary_key_for_select)
51
+ else
52
+ cte_query.select(primary_key_for_select, column_for_select)
53
+ end
46
54
  cte_query = cte_query.limit(select_limit) if select_limit
47
55
  cte_type = supports_cte_materialization_specifiers? ? :MATERIALIZED : :""
48
56
  composed_cte = Arel::Nodes::As.new(cte_table, Arel::Nodes::UnaryOperation.new(cte_type, cte_query.arel))
@@ -216,6 +216,7 @@ module GoodJob
216
216
  return unless execute_async?
217
217
 
218
218
  @capsule.start
219
+ @capsule.lower_thread_priority = true if GoodJob.configuration.lower_thread_priority.in?([true, nil])
219
220
  @_async_started = true
220
221
  end
221
222
 
@@ -24,6 +24,7 @@ module GoodJob
24
24
 
25
25
  @shared_executor = GoodJob::SharedExecutor.new
26
26
  @tracker = GoodJob::CapsuleTracker.new(executor: @shared_executor)
27
+ @lower_thread_priority = nil
27
28
 
28
29
  self.class.instances << self
29
30
  end
@@ -38,7 +39,9 @@ module GoodJob
38
39
 
39
40
  @notifier = GoodJob::Notifier.new(enable_listening: configuration.enable_listen_notify, capsule: self, executor: @shared_executor)
40
41
  @poller = GoodJob::Poller.new(poll_interval: configuration.poll_interval)
41
- @multi_scheduler = GoodJob::MultiScheduler.from_configuration(configuration, capsule: self, warm_cache_on_initialize: true)
42
+ @multi_scheduler = GoodJob::MultiScheduler.from_configuration(configuration, capsule: self, warm_cache_on_initialize: true).tap do |multischeduler|
43
+ multischeduler.lower_thread_priority = @lower_thread_priority unless @lower_thread_priority.nil?
44
+ end
42
45
  @notifier.recipients.push([@multi_scheduler, :create_thread])
43
46
  @poller.recipients.push(-> { @multi_scheduler.create_thread({ fanout: true }) })
44
47
 
@@ -110,6 +113,11 @@ module GoodJob
110
113
  @tracker.process_id
111
114
  end
112
115
 
116
+ def lower_thread_priority=(value)
117
+ @lower_thread_priority = value
118
+ @multi_scheduler&.lower_thread_priority = value
119
+ end
120
+
113
121
  private
114
122
 
115
123
  def configuration
@@ -383,6 +383,14 @@ module GoodJob
383
383
  end || false
384
384
  end
385
385
 
386
+ def lower_thread_priority
387
+ return options[:lower_thread_priority] unless options[:lower_thread_priority].nil?
388
+ return rails_config[:lower_thread_priority] unless rails_config[:lower_thread_priority].nil?
389
+ return ActiveModel::Type::Boolean.new.cast(env['GOOD_JOB_LOWER_THREAD_PRIORITY']) unless env['GOOD_JOB_LOWER_THREAD_PRIORITY'].nil?
390
+
391
+ nil
392
+ end
393
+
386
394
  # Whether to take an advisory lock on the process record in the notifier reactor.
387
395
  # @return [Boolean]
388
396
  def advisory_lock_heartbeat
@@ -19,7 +19,8 @@ module GoodJob
19
19
  max_cache: configuration.max_cache,
20
20
  warm_cache_on_initialize: warm_cache_on_initialize,
21
21
  cleanup_interval_seconds: configuration.cleanup_interval_seconds,
22
- cleanup_interval_jobs: configuration.cleanup_interval_jobs
22
+ cleanup_interval_jobs: configuration.cleanup_interval_jobs,
23
+ lower_thread_priority: configuration.lower_thread_priority
23
24
  )
24
25
  end
25
26
 
@@ -85,6 +86,12 @@ module GoodJob
85
86
  end
86
87
  end
87
88
 
89
+ def lower_thread_priority=(value)
90
+ schedulers.each do |scheduler|
91
+ scheduler.lower_thread_priority = value
92
+ end
93
+ end
94
+
88
95
  def stats
89
96
  scheduler_stats = schedulers.map(&:stats)
90
97
 
@@ -29,6 +29,9 @@ module GoodJob # :nodoc:
29
29
  fallback_policy: :discard,
30
30
  }.freeze
31
31
 
32
+ # In CRuby, this sets the thread quantum to ~12.5ms ( 100ms * 2^(-3) ).
33
+ LOW_THREAD_PRIORITY = -3
34
+
32
35
  # @!attribute [r] instances
33
36
  # @!scope class
34
37
  # List of all instantiated Schedulers in the current process.
@@ -39,13 +42,18 @@ module GoodJob # :nodoc:
39
42
  # @return [String]
40
43
  attr_reader :name
41
44
 
45
+ # Whether to lower the thread priority to a fixed value
46
+ # @return [Boolean]
47
+ attr_accessor :lower_thread_priority
48
+
42
49
  # @param performer [GoodJob::JobPerformer]
43
50
  # @param max_threads [Numeric, nil] number of seconds between polls for jobs
44
51
  # @param max_cache [Numeric, nil] maximum number of scheduled jobs to cache in memory
45
52
  # @param warm_cache_on_initialize [Boolean] whether to warm the cache immediately, or manually by calling +warm_cache+
46
53
  # @param cleanup_interval_seconds [Numeric, nil] number of seconds between cleaning up job records
47
54
  # @param cleanup_interval_jobs [Numeric, nil] number of executed jobs between cleaning up job records
48
- def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize: false, cleanup_interval_seconds: nil, cleanup_interval_jobs: nil)
55
+ # @param lower_thread_priority [Boolean] whether to lower the thread priority of execution threads
56
+ def initialize(performer, max_threads: nil, max_cache: nil, warm_cache_on_initialize: false, cleanup_interval_seconds: nil, cleanup_interval_jobs: nil, lower_thread_priority: false)
49
57
  raise ArgumentError, "Performer argument must implement #next" unless performer.respond_to?(:next)
50
58
 
51
59
  @performer = performer
@@ -62,6 +70,8 @@ module GoodJob # :nodoc:
62
70
  @cleanup_tracker = CleanupTracker.new(cleanup_interval_seconds: cleanup_interval_seconds, cleanup_interval_jobs: cleanup_interval_jobs)
63
71
  @executor_options[:name] = name
64
72
 
73
+ self.lower_thread_priority = lower_thread_priority
74
+
65
75
  create_executor
66
76
  warm_cache if warm_cache_on_initialize
67
77
  self.class.instances << self
@@ -271,6 +281,7 @@ module GoodJob # :nodoc:
271
281
  future = Concurrent::ScheduledTask.new(delay, args: [self, performer], executor: executor, timer_set: timer_set) do |thr_scheduler, thr_performer|
272
282
  Thread.current.name = Thread.current.name.sub("-worker-", "-thread-") if Thread.current.name
273
283
  Thread.current[:good_job_scheduler] = thr_scheduler
284
+ Thread.current.priority = -3 if thr_scheduler.lower_thread_priority
274
285
 
275
286
  Rails.application.reloader.wrap do
276
287
  thr_performer.next do |found|
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GoodJob
4
4
  # GoodJob gem version.
5
- VERSION = '4.5.0'
5
+ VERSION = '4.6.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
@@ -203,11 +203,11 @@ module GoodJob
203
203
  # If you are preserving job records this way, use this method regularly to
204
204
  # destroy old records and preserve space in your database.
205
205
  # @param older_than [nil,Numeric,ActiveSupport::Duration] Jobs older than this will be destroyed (default: +86400+).
206
+ # @param include_discarded [Boolean] Whether or not to destroy discarded jobs (default: per +cleanup_discarded_jobs+ config option)
206
207
  # @return [Integer] Number of job execution records and batches that were destroyed.
207
- def self.cleanup_preserved_jobs(older_than: nil, in_batches_of: 1_000)
208
+ def self.cleanup_preserved_jobs(older_than: nil, in_batches_of: 1_000, include_discarded: GoodJob.configuration.cleanup_discarded_jobs?)
208
209
  older_than ||= GoodJob.configuration.cleanup_preserved_jobs_before_seconds_ago
209
210
  timestamp = Time.current - older_than
210
- include_discarded = GoodJob.configuration.cleanup_discarded_jobs?
211
211
 
212
212
  ActiveSupport::Notifications.instrument("cleanup_preserved_jobs.good_job", { older_than: older_than, timestamp: timestamp }) do |payload|
213
213
  deleted_jobs_count = 0
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: 4.5.0
4
+ version: 4.6.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-11-22 00:00:00.000000000 Z
11
+ date: 2024-12-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob