solid_queue_guard 1.2.5 → 1.3.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: 1893e95d5ab5c66e556b93b743f0a981b62509716b11350b546941af696cf282
4
- data.tar.gz: 44d23bf1b5f8900a50c84446a2abaaba97bba4689979d5f0799d148e7edee06d
3
+ metadata.gz: 437b060eb21ee49c674d1f5ddbb34471209bbecd11eef1a99a562249f134b1f1
4
+ data.tar.gz: 0fb8e3a58e206ee4a0b78a4c815efb7397c1d5d55e153360d7655eee21ff81fa
5
5
  SHA512:
6
- metadata.gz: 3a90a43b4541edd2f96ad9795708a016934ac6820998c83c4217be5902c8210fdf8ba0bb07ba021922cc297f58caf23a704640231fc6f65b28842d32e9bb3e79
7
- data.tar.gz: 50df8d5b56540152da201bf0a71acd9cb9f8d1ae9a11b180d66f5d597e274982c54bb16bec3d82d5cc1997cd12c7fa128db983a1a10e303c4e93cc47e8b9a954
6
+ metadata.gz: 9fd08d490a2dff9d8e4915ed2742a660433c781a3244c321097715aac51c73729c91b5ad2cbcc90200c53c336c6a5f7cd80feb3eaebd426e025426700b5f1db5
7
+ data.tar.gz: da19ef71cf30e339ac2576a1749064f4ae904491bc0a67fdd61a9593feea6f2a48a051d13f260d6d0f739a43ed5aaddc5b5ef51e2183f98a58b36dbfdb3888bd
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.1] - 2026-07-31
9
+
10
+ ### Fixed
11
+
12
+ - `thread_pool` and topology pool recommendations work with Solid Queue 1.6+, which renamed
13
+ `estimated_number_of_threads` to `estimated_database_pool_size`. Guard now reads whichever
14
+ private helper the installed gem exposes.
15
+
16
+ ## [1.3.0] - 2026-07-29
17
+
18
+ ### Changed
19
+
20
+ - `process_heartbeat_config` no longer warns just because `process_heartbeat_interval` and
21
+ `process_alive_threshold` keep the Solid Queue defaults — the defaults now pass. The check
22
+ validates the relationship between the values instead: it fails when `process_alive_threshold`
23
+ is not above the heartbeat interval, and warns when the margin tolerates less than
24
+ `min_margin` heartbeats (default 2) or when Guard's `stale_process_threshold` is above
25
+ `process_alive_threshold`
26
+ - `config.checks.process_heartbeat_config = { min_margin: 3 }` tunes the required margin
27
+
8
28
  ## [1.2.5] - 2026-07-10
9
29
 
10
30
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- solid_queue_guard (1.2.5)
4
+ solid_queue_guard (1.3.1)
5
5
  actionpack (>= 7.1, < 9.0)
6
6
  activejob (>= 7.1, < 9.0)
7
7
  activerecord (>= 7.1, < 9.0)
@@ -3,26 +3,107 @@
3
3
  module SolidQueueGuard
4
4
  module Checks
5
5
  module Config
6
+ # Solid Queue prunes any process whose heartbeat is older than
7
+ # process_alive_threshold, releasing the executions it had claimed. A live
8
+ # worker that merely delayed a heartbeat is pruned the same way, so the
9
+ # threshold must leave room for more than one missed beat.
6
10
  class ProcessHeartbeatConfigCheck < Base
7
- DEFAULT_HEARTBEAT_INTERVAL = 60.seconds
8
- DEFAULT_ALIVE_THRESHOLD = 5.minutes
11
+ DEFAULT_MIN_MARGIN = 2
9
12
 
10
13
  def call
11
- heartbeat_interval = SolidQueue.process_heartbeat_interval
12
- alive_threshold = SolidQueue.process_alive_threshold
13
-
14
- if heartbeat_interval == DEFAULT_HEARTBEAT_INTERVAL && alive_threshold == DEFAULT_ALIVE_THRESHOLD
15
- warn(
16
- 'process_heartbeat_config',
17
- 'Process heartbeat thresholds use defaults (interval: 60s, alive: 5m)',
18
- suggestion: 'Consider customizing process_alive_threshold for your deployment latency requirements'
19
- )
20
- else
21
- pass(
22
- 'process_heartbeat_config',
23
- "Process heartbeat interval: #{heartbeat_interval.inspect}, alive threshold: #{alive_threshold.inspect}"
24
- )
25
- end
14
+ return invalid_heartbeat_interval unless heartbeat_interval.positive?
15
+ return alive_threshold_too_low if alive_threshold <= heartbeat_interval
16
+ return margin_too_tight if margin < min_margin
17
+ return guard_threshold_above_alive if stale_process_threshold > alive_threshold
18
+
19
+ healthy_thresholds
20
+ end
21
+
22
+ private
23
+
24
+ def healthy_thresholds
25
+ pass(
26
+ check_id,
27
+ "Heartbeat interval: #{humanize(heartbeat_interval)}, " \
28
+ "alive threshold: #{humanize(alive_threshold)} (#{margin.round(1)}x margin)",
29
+ metadata: base_metadata
30
+ )
31
+ end
32
+
33
+ def invalid_heartbeat_interval
34
+ failure(
35
+ check_id,
36
+ 'process_heartbeat_interval must be a positive duration',
37
+ suggestion: 'Set SolidQueue.process_heartbeat_interval to a positive value (default: 60 seconds)',
38
+ metadata: base_metadata
39
+ )
40
+ end
41
+
42
+ def alive_threshold_too_low
43
+ failure(
44
+ check_id,
45
+ "process_alive_threshold (#{humanize(alive_threshold)}) is not greater than " \
46
+ "process_heartbeat_interval (#{humanize(heartbeat_interval)})",
47
+ suggestion: "Raise process_alive_threshold to at least #{humanize(recommended_threshold)} " \
48
+ 'so a single late heartbeat does not prune a live process',
49
+ metadata: base_metadata
50
+ )
51
+ end
52
+
53
+ def margin_too_tight
54
+ warn(
55
+ check_id,
56
+ "process_alive_threshold (#{humanize(alive_threshold)}) tolerates less than " \
57
+ "#{min_margin} heartbeats of #{humanize(heartbeat_interval)}",
58
+ suggestion: "Raise process_alive_threshold to at least #{humanize(recommended_threshold)} " \
59
+ 'so a single late heartbeat does not release claimed jobs',
60
+ metadata: base_metadata
61
+ )
62
+ end
63
+
64
+ def guard_threshold_above_alive
65
+ warn(
66
+ check_id,
67
+ "Guard stale_process_threshold (#{humanize(stale_process_threshold)}) is above " \
68
+ "process_alive_threshold (#{humanize(alive_threshold)})",
69
+ suggestion: "Lower stale_process_threshold to at most #{humanize(alive_threshold)}; " \
70
+ 'Solid Queue prunes dead process rows before Guard would report them as stale',
71
+ metadata: base_metadata.merge(stale_process_threshold: stale_process_threshold)
72
+ )
73
+ end
74
+
75
+ def heartbeat_interval
76
+ @heartbeat_interval ||= SolidQueue.process_heartbeat_interval.to_i
77
+ end
78
+
79
+ def alive_threshold
80
+ @alive_threshold ||= SolidQueue.process_alive_threshold.to_i
81
+ end
82
+
83
+ def stale_process_threshold
84
+ @stale_process_threshold ||= guard_config.check_setting(
85
+ :stale_process, :threshold, guard_config.stale_process_threshold
86
+ ).to_i
87
+ end
88
+
89
+ def min_margin
90
+ @min_margin ||= check_setting(:min_margin, DEFAULT_MIN_MARGIN)
91
+ end
92
+
93
+ def margin
94
+ @margin ||= alive_threshold / heartbeat_interval.to_f
95
+ end
96
+
97
+ def recommended_threshold
98
+ heartbeat_interval * min_margin
99
+ end
100
+
101
+ def base_metadata
102
+ { heartbeat_interval: heartbeat_interval, alive_threshold: alive_threshold }
103
+ end
104
+
105
+ def humanize(seconds)
106
+ ActiveSupport::Duration.build(seconds).inspect
26
107
  end
27
108
  end
28
109
  end
@@ -5,7 +5,7 @@ module SolidQueueGuard
5
5
  module Config
6
6
  class ThreadPoolCheck < Base
7
7
  def call
8
- required_threads = solid_queue_configuration.send(:estimated_number_of_threads)
8
+ required_pool = ConfigurationSizing.estimated_database_pool_size(solid_queue_configuration)
9
9
  pool_size = SolidQueue::Record.connection_pool&.size
10
10
 
11
11
  return skip('thread_pool', 'Queue database connection pool is not available') if pool_size.nil?
@@ -17,18 +17,18 @@ module SolidQueueGuard
17
17
  )
18
18
  end.max || 1
19
19
 
20
- if pool_size >= required_threads
20
+ if pool_size >= required_pool
21
21
  pass(
22
22
  'thread_pool',
23
23
  "Worker threads: #{max_worker_threads}, queue DB pool: #{pool_size}",
24
- metadata: { threads: max_worker_threads, pool: pool_size, required: required_threads }
24
+ metadata: { threads: max_worker_threads, pool: pool_size, required: required_pool }
25
25
  )
26
26
  else
27
27
  failure(
28
28
  'thread_pool',
29
29
  "Worker threads: #{max_worker_threads}, queue DB pool: #{pool_size}",
30
- suggestion: "Increase queue DB pool to at least #{required_threads} or reduce worker threads",
31
- metadata: { threads: max_worker_threads, pool: pool_size, required: required_threads }
30
+ suggestion: "Increase queue DB pool to at least #{required_pool} or reduce worker threads",
31
+ metadata: { threads: max_worker_threads, pool: pool_size, required: required_pool }
32
32
  )
33
33
  end
34
34
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ # Reads Solid Queue's estimated queue-DB pool requirement across gem versions.
5
+ #
6
+ # Solid Queue 1.6 renamed the private helper from +estimated_number_of_threads+
7
+ # to +estimated_database_pool_size+ (fiber workers need a different estimate).
8
+ # Both return the same value for classic thread workers: max capacity + 2.
9
+ module ConfigurationSizing
10
+ module_function
11
+
12
+ def estimated_database_pool_size(configuration = SolidQueue::Configuration.new)
13
+ if configuration.respond_to?(:estimated_database_pool_size, true)
14
+ configuration.send(:estimated_database_pool_size)
15
+ elsif configuration.respond_to?(:estimated_number_of_threads, true)
16
+ configuration.send(:estimated_number_of_threads)
17
+ else
18
+ raise NoMethodError,
19
+ 'SolidQueue::Configuration has neither estimated_database_pool_size ' \
20
+ 'nor estimated_number_of_threads'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -33,11 +33,11 @@ module SolidQueueGuard
33
33
  def pool_recommendations
34
34
  return async_pool_recommendation if PumaPluginSupport.async_supervisor_mode?
35
35
 
36
- required_threads = configuration.estimated_number_of_threads
36
+ required_pool = ConfigurationSizing.estimated_database_pool_size(configuration)
37
37
  pool_size = SolidQueue::Record.connection_pool&.size
38
- return [] if pool_size.nil? || required_threads <= pool_size
38
+ return [] if pool_size.nil? || required_pool <= pool_size
39
39
 
40
- ["Increase queue DB pool to at least #{required_threads + 2} (currently #{pool_size})"]
40
+ ["Increase queue DB pool to at least #{required_pool} (currently #{pool_size})"]
41
41
  rescue StandardError
42
42
  []
43
43
  end
@@ -62,11 +62,11 @@ module SolidQueueGuard
62
62
  end
63
63
 
64
64
  def async_pool_recommendation
65
- required_threads = configuration.estimated_number_of_threads
65
+ required_pool = ConfigurationSizing.estimated_database_pool_size(configuration)
66
66
  pool_size = SolidQueue::Record.connection_pool&.size
67
- return [] if pool_size.nil? || required_threads <= pool_size
67
+ return [] if pool_size.nil? || required_pool <= pool_size
68
68
 
69
- ["Increase queue DB pool to at least #{required_threads + 2} " \
69
+ ["Increase queue DB pool to at least #{required_pool} " \
70
70
  "for async supervisor mode (currently #{pool_size})"]
71
71
  rescue StandardError
72
72
  []
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueGuard
4
- VERSION = '1.2.5'
4
+ VERSION = '1.3.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_queue_guard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.5
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rafael Pissardo
@@ -358,6 +358,7 @@ files:
358
358
  - lib/solid_queue_guard/checks/runtime/stale_process_check.rb
359
359
  - lib/solid_queue_guard/cli.rb
360
360
  - lib/solid_queue_guard/configuration.rb
361
+ - lib/solid_queue_guard/configuration_sizing.rb
361
362
  - lib/solid_queue_guard/engine.rb
362
363
  - lib/solid_queue_guard/formatters/json.rb
363
364
  - lib/solid_queue_guard/formatters/terminal.rb