gruf-queue 0.1.1 → 0.1.3

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: b0aed8b2822b571278254b3fa5838fcee2bd1c709ce7397bf4dbe9fc296f8847
4
- data.tar.gz: e4972e7017721f7c6d1f2924c6b362bb0bb08705b4ef63b31f9ee2465904d953
3
+ metadata.gz: 16b3387c40ed0f1175b3b6ee1127a15825788f17ae1ccd829385462d58c49913
4
+ data.tar.gz: d6694faf0538b2f6085a318433389ccf073c2f93876e78f1fb770bc061f6273d
5
5
  SHA512:
6
- metadata.gz: '09802fce427e339133d11fd72776a61061334e0b29e38e28628a636948830b48280fdc6b89e60dedad2e4a6fcce6355e21c1c1c79ff3c27691697f4eac560624'
7
- data.tar.gz: 26ae3647141bc9bee23607743bc94c19f42a2b7bd81bf3a9440c3bd6f86d01122c1b4e5f81144ee6a009595aa445e685cc7f33bba5fdf604a07acacb013182df
6
+ metadata.gz: b69971940855b247545a1488522d7b0abb9833c9d2eea4b4f8399e664fe9910f3a044a07e967e4cb3f8da91188b758d6ebdf5fd3097b477a51cf707d3275e13e
7
+ data.tar.gz: bc4d7414d8fdb9223721928facbb5b17db77bd753904a1419af72729ebaae22dcbcaf60244a53fca058ece126d9b1c16a284a3eb1ad0b7fbb92a1352a85d7591
data/CHANGELOG.md CHANGED
@@ -5,7 +5,63 @@ 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.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
- ## [0.1.0] - 2024-01-01
8
+ ## [0.1.3]
9
+
10
+ ### Changed
11
+ - **BREAKING**: Removed ServerFactory - use `QueuedRpcServer.new()` directly instead
12
+ - **BREAKING**: Removed Configuration wrapper - use Gruf's native configuration
13
+ - **BREAKING**: Removed custom ConnectionReset interceptor - use gruf's built-in `Gruf::Interceptors::ActiveRecord::ConnectionReset`
14
+ - Simplified plugin.rb from 104 to 60 lines by removing complex validation logic
15
+ - Merged pool.rb into pool_enhancements.rb for better organization
16
+
17
+ ### Removed
18
+ - `Gruf::Queue::ServerFactory` - unnecessary abstraction layer
19
+ - `Gruf::Queue::Configuration` - wrapper around Gruf's native config
20
+ - `Gruf::Queue::Interceptors::ConnectionReset` - replaced with gruf's built-in
21
+ - `Gruf::Queue::Pool` class - functionality moved to PoolEnhancements module
22
+ - Obsolete test files and redundant test cases
23
+
24
+ ### Improved
25
+ - **730 lines of code removed** - dramatically simplified codebase
26
+ - More direct and intuitive API without unnecessary abstractions
27
+ - Better integration with gruf's native features
28
+ - Cleaner test suite with eliminated logging noise
29
+ - Improved maintainability and reduced complexity
30
+
31
+ ### Fixed
32
+ - Suppressed Gruf logger output during tests for cleaner test runs
33
+ - Updated all documentation and examples to reflect simplified API
34
+
35
+ ## [0.1.2]
36
+
37
+ ### Changed
38
+ - **BREAKING**: Simplified logging system by removing custom log helpers
39
+ - Removed all custom logging methods (log_structured, log_debug, log_error)
40
+ - Simplified error handling to use Gruf.logger directly only where critical
41
+ - Eliminated redundant logging as it's not the gem's responsibility
42
+ - Removed excessive YARD documentation and inline comments for better readability
43
+ - Replaced const replacement with safer module prepend pattern
44
+ - Enhanced GRPC::Pool using PoolEnhancements module without class replacement
45
+ - Improved error handling and recovery mechanisms
46
+ - Updated test suite to handle new module prepend architecture
47
+
48
+ ### Improved
49
+ - Code is now more readable with essential documentation only
50
+ - Reduced complexity following single responsibility principle
51
+ - Applications handle their own logging strategy
52
+ - Fixed all rubocop style issues
53
+
54
+ ### Fixed
55
+ - **CRITICAL**: Fixed parameter passing to parent GRPC::RpcServer class
56
+ - Fixed Rails initialization timing issues with auto-install
57
+ - Fixed thread pool management with proper parameter inheritance
58
+
59
+ ### Added
60
+ - PoolEnhancements module for clean GRPC::Pool extension
61
+ - Improved thread safety and pool management
62
+ - Better error isolation in worker threads
63
+
64
+ ## [0.1.0]
9
65
 
10
66
  ### Added
11
67
  - Initial release of gruf-queue
@@ -28,4 +84,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
84
  - **Observability**: Structured logging with metadata for monitoring and debugging
29
85
  - **Customization**: Flexible configuration options for different deployment scenarios
30
86
 
31
- [0.1.0]: https://github.com/ether-moon/gruf-queue/releases/tag/v0.1.0
87
+ [0.1.0]: https://github.com/ether-moon/gruf-queue/releases/tag/v0.1.0
data/README.md CHANGED
@@ -84,7 +84,7 @@ Gruf.configure do |config|
84
84
  config.rpc_server = Gruf::Queue::QueuedRpcServer
85
85
 
86
86
  config.interceptors.use(
87
- Gruf::Queue::Interceptors::ConnectionReset,
87
+ Gruf::Interceptors::ActiveRecord::ConnectionReset,
88
88
  enabled: true,
89
89
  target_classes: [ActiveRecord::Base]
90
90
  )
@@ -108,19 +108,15 @@ Gruf.configure do |config|
108
108
  end
109
109
  ```
110
110
 
111
- ### Using Server Factory
111
+ ### Custom Server Configuration
112
112
 
113
- For advanced server creation:
113
+ For custom server instances with specific options:
114
114
 
115
115
  ```ruby
116
- # Create custom server instances
117
- server = Gruf::Queue::ServerFactory.create_server(
116
+ # Create custom server instances directly
117
+ server = Gruf::Queue::QueuedRpcServer.new(
118
118
  pool_size: 15,
119
- max_waiting_requests: 30,
120
- server: Gruf::Queue::QueuedRpcServer,
121
- interceptors: [
122
- Gruf::Queue::Interceptors::ConnectionReset
123
- ]
119
+ max_waiting_requests: 30
124
120
  )
125
121
 
126
122
  Gruf::Server.new(
@@ -139,7 +135,7 @@ The `ConnectionReset` interceptor automatically manages database connections:
139
135
  # Customizable with additional target classes:
140
136
  Gruf.configure do |config|
141
137
  config.interceptors.use(
142
- Gruf::Queue::Interceptors::ConnectionReset,
138
+ Gruf::Interceptors::ActiveRecord::ConnectionReset,
143
139
  enabled: true,
144
140
  target_classes: [ActiveRecord::Base, CustomConnectionClass]
145
141
  )
@@ -162,7 +158,7 @@ Advanced thread pool implementation:
162
158
  - Structured logging with thread identification
163
159
  - Comprehensive error isolation and recovery
164
160
 
165
- #### `Gruf::Queue::Interceptors::ConnectionReset`
161
+ #### `Gruf::Interceptors::ActiveRecord::ConnectionReset`
166
162
  Smart database connection management:
167
163
  - Automatically resets ActiveRecord connections after each request
168
164
  - Validates connection handlers before attempting reset
@@ -1,155 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'pool_enhancements'
4
+
3
5
  module Gruf
4
6
  module Queue
5
7
  # Plugin management for gruf-queue integration.
6
8
  #
7
- # Provides idempotent installation and configuration of queue-specific settings
8
- # including RPC server setup and interceptor registration.
9
- #
10
- # @example Install plugin
11
- # Gruf::Queue::Plugin.install! # => true
12
- #
13
- # @example Check installation status
14
- # Gruf::Queue::Plugin.installed? # => true
9
+ # Provides simple installation and configuration of queue-specific settings.
15
10
  class Plugin
16
11
  class << self
17
- # Install and configure the gruf-queue plugin.
18
- # This method is idempotent and can be called multiple times safely.
19
- #
20
- # @return [Boolean] true if installation completed, false if already installed
21
12
  def install!
22
13
  return false if @installed
23
14
 
24
- begin
25
- # Configure Gruf with queue-specific settings
26
- Configuration.configure unless Configuration.configured?
27
-
28
- # Configure Gruf with all queue-specific settings
29
- Gruf.configure do |config|
30
- configure_rpc_server(config)
31
- configure_interceptors(config)
32
- end
33
-
34
- @installed = true
35
- true
36
- rescue StandardError => e
37
- # Log the error but don't raise to avoid breaking the application startup
38
- log_installation_error(e)
39
- false
40
- end
15
+ enhance_grpc_pool
16
+ configure_gruf
17
+ @installed = true
18
+ true
19
+ rescue StandardError
20
+ false
41
21
  end
42
22
 
43
- # Check installation status.
44
- #
45
- # @return [Boolean] true if installed, false otherwise
46
23
  def installed?
47
24
  !!@installed
48
25
  end
49
26
 
50
- # Reset installation state for testing.
51
- #
52
- # @return [void]
53
27
  def reset!
54
28
  @installed = false
55
- # Also reset the configuration state
56
- Configuration.reset! if Configuration.respond_to?(:reset!)
57
29
  end
58
30
 
59
31
  private
60
32
 
61
- # Configure RPC server to use QueuedRpcServer.
62
- #
63
- # @param config [Object] Gruf configuration object
64
- # @return [void]
65
- # @api private
66
- def configure_rpc_server(config)
67
- config.rpc_server = QueuedRpcServer
68
- end
69
-
70
- # Configure interceptors if ActiveRecord is available.
71
- #
72
- # @param config [Object] Gruf configuration object
73
- # @return [void]
74
- # @api private
75
- def configure_interceptors(config)
76
- return unless defined?(ActiveRecord)
77
-
78
- validate_active_record_availability
79
- ensure_interceptors_registry_available(config)
80
-
81
- config.interceptors.use(
82
- Interceptors::ConnectionReset,
83
- enabled: true,
84
- target_classes: [ActiveRecord::Base],
85
- )
86
- end
87
-
88
- # Validate ActiveRecord connection handler availability.
89
- #
90
- # @raise [StandardError] if ActiveRecord doesn't support connection_handler
91
- # @return [void]
92
- # @api private
93
- def validate_active_record_availability
94
- return if ActiveRecord::Base.respond_to?(:connection_handler)
95
-
96
- raise StandardError, 'ActiveRecord::Base does not support connection_handler'
97
- end
98
-
99
- # Ensure interceptors registry is available and initialized.
100
- #
101
- # Handles Rails initialization timing issues where gruf-queue loads
102
- # before Gruf's configuration is fully initialized.
103
- #
104
- # @param config [Object] Gruf configuration object
105
- # @return [void]
106
- # @raise [StandardError] if interceptors registry cannot be initialized
107
- # @api private
108
- def ensure_interceptors_registry_available(config)
109
- # Check if interceptors registry exists and is initialized
110
- return if config.respond_to?(:interceptors) && !config.interceptors.nil?
111
-
112
- # Initialize interceptors registry if missing
113
- begin
114
- require 'gruf/interceptors/registry'
115
- rescue LoadError => e
116
- raise StandardError, "Failed to load Gruf interceptors registry: #{e.message}"
117
- end
118
-
119
- if config.respond_to?(:interceptors=)
120
- config.interceptors = ::Gruf::Interceptors::Registry.new
121
- elsif config.respond_to?(:define_singleton_method)
122
- # Fallback: define interceptors accessor methods dynamically
123
- registry = ::Gruf::Interceptors::Registry.new
124
- config.define_singleton_method(:interceptors) { registry }
125
- config.define_singleton_method(:interceptors=) { |val| registry = val }
126
- else
127
- raise StandardError, 'Cannot initialize interceptors registry: configuration object lacks required methods'
128
- end
129
-
130
- # Verify initialization was successful
131
- return if config.respond_to?(:interceptors) && config.interceptors.respond_to?(:use)
33
+ def enhance_grpc_pool
34
+ return if ::GRPC::Pool.included_modules.include?(Gruf::Queue::PoolEnhancements)
132
35
 
133
- raise StandardError, 'Interceptors registry initialization failed: missing use method'
36
+ ::GRPC::Pool.prepend(Gruf::Queue::PoolEnhancements)
134
37
  end
135
38
 
136
- # Log installation error using available logger.
137
- #
138
- # @param error [StandardError] Error to log
139
- # @return [void]
140
- # @api private
141
- def log_installation_error(error)
142
- message = "Failed to install gruf-queue plugin: #{error.message}"
143
-
144
- # Skip error logging in test environment
145
- return if ENV['RACK_ENV'] == 'test' || ENV['RAILS_ENV'] == 'test' || defined?(RSpec)
146
-
147
- if defined?(Rails) && Rails.logger.respond_to?(:error)
148
- Rails.logger.error(message)
149
- elsif defined?(GRPC) && GRPC.logger.respond_to?(:error)
150
- GRPC.logger.error(message)
151
- else
152
- warn message
39
+ def configure_gruf
40
+ Gruf.configure do |config|
41
+ # Set default server options compatible with QueuedRpcServer
42
+ config.rpc_server_options = {
43
+ pool_size: QueuedRpcServer::DEFAULT_POOL_SIZE,
44
+ max_waiting_requests: QueuedRpcServer::DEFAULT_MAX_WAITING_REQUESTS,
45
+ poll_period: QueuedRpcServer::DEFAULT_POLL_PERIOD,
46
+ pool_keep_alive: PoolEnhancements::DEFAULT_KEEP_ALIVE,
47
+ }
48
+
49
+ if defined?(ActiveRecord)
50
+ require 'gruf/interceptors/active_record/connection_reset'
51
+ # Configure interceptors at the global level
52
+ if Gruf.respond_to?(:interceptors)
53
+ Gruf.interceptors.use(
54
+ Gruf::Interceptors::ActiveRecord::ConnectionReset,
55
+ target_classes: [ActiveRecord::Base],
56
+ )
57
+ end
58
+ end
153
59
  end
154
60
  end
155
61
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gruf
4
+ module Queue
5
+ # Enhanced functionality for GRPC::Pool without intrusive modifications.
6
+ #
7
+ # Provides essential queue management features while preserving the original
8
+ # GRPC::Pool class structure and inheritance hierarchy.
9
+ #
10
+ # @example Usage
11
+ # GRPC::Pool.prepend(Gruf::Queue::PoolEnhancements)
12
+ module PoolEnhancements
13
+ # Default keep-alive time for worker threads (in seconds)
14
+ DEFAULT_KEEP_ALIVE = 600
15
+
16
+ def jobs_waiting
17
+ @jobs&.size || 0
18
+ end
19
+
20
+ def schedule(*args, &blk)
21
+ return false if blk.nil?
22
+
23
+ super
24
+ rescue StandardError
25
+ false
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'pool'
3
+ require_relative 'pool_enhancements'
4
4
 
5
5
  module Gruf
6
6
  module Queue
@@ -30,32 +30,19 @@ module Gruf
30
30
  # Error message for resource exhaustion
31
31
  RESOURCE_EXHAUSTED_MESSAGE = 'No free threads in thread pool'
32
32
 
33
- # Initialize the QueuedRpcServer with custom pool settings.
34
- #
35
- # @param pool_size [Integer] Number of worker threads in the pool
36
- # @param max_waiting_requests [Integer] Maximum requests to queue before rejecting
37
- # @param pool_keep_alive [Integer] Thread keep-alive time in seconds
38
- # @param poll_period [Integer] Server polling interval in seconds
39
- # @param args [Hash] Additional arguments passed to GRPC::RpcServer
40
33
  def initialize(pool_size: DEFAULT_POOL_SIZE,
41
34
  max_waiting_requests: DEFAULT_MAX_WAITING_REQUESTS,
42
- pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
35
+ pool_keep_alive: PoolEnhancements::DEFAULT_KEEP_ALIVE,
43
36
  poll_period: DEFAULT_POLL_PERIOD,
44
37
  **args)
45
- super(**args)
38
+ super
46
39
 
47
40
  @pool_size = pool_size
48
41
  @max_waiting_requests = max_waiting_requests
49
42
  @pool_keep_alive = pool_keep_alive
50
43
  @poll_period = poll_period
51
-
52
- @pool = Gruf::Queue::Pool.new(@pool_size, keep_alive: @pool_keep_alive)
53
44
  end
54
45
 
55
- # Check if server can handle the request based on current queue load.
56
- #
57
- # @param an_rpc [Object] The incoming RPC request
58
- # @return [Object, false] The RPC if available, false if resource exhausted
59
46
  def available?(an_rpc)
60
47
  job_count = safe_job_count
61
48
  return an_rpc if job_count < @max_waiting_requests
@@ -66,26 +53,19 @@ module Gruf
66
53
 
67
54
  private
68
55
 
69
- # Get current job count from pool with error handling.
70
- #
71
- # @return [Integer] Number of jobs waiting, or max_waiting_requests on error
72
56
  def safe_job_count
57
+ return 0 unless @pool
58
+
73
59
  @pool.jobs_waiting
74
60
  rescue StandardError => e
75
- ::GRPC.logger.warn("Failed to get job count: #{e.message}")
76
- # Assume maximum load if we can't determine the actual count
61
+ Gruf.logger.warn("Failed to get job count: #{e.message}") if defined?(Gruf) && Gruf.respond_to?(:logger)
77
62
  @max_waiting_requests
78
63
  end
79
64
 
80
- # Send RESOURCE_EXHAUSTED status to client with error handling.
81
- #
82
- # @param an_rpc [Object] The RPC to send error response to
83
- # @return [void]
84
65
  def send_resource_exhausted_response(an_rpc)
85
- ::GRPC.logger.warn('no free worker threads currently') if ::GRPC.logger.respond_to?(:warn)
66
+ Gruf.logger.warn('no free worker threads currently') if defined?(Gruf) && Gruf.respond_to?(:logger)
86
67
 
87
68
  begin
88
- # Try calling send_status directly on the RPC object first (for test mocks)
89
69
  if an_rpc.respond_to?(:send_status)
90
70
  an_rpc.send_status(
91
71
  ::GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
@@ -95,7 +75,6 @@ module Gruf
95
75
  return
96
76
  end
97
77
 
98
- # Create a new active call that knows that metadata hasn't been sent yet
99
78
  active_call = ::GRPC::ActiveCall.new(
100
79
  an_rpc.call,
101
80
  NOOP_PROC,
@@ -110,15 +89,13 @@ module Gruf
110
89
  RESOURCE_EXHAUSTED_MESSAGE,
111
90
  )
112
91
  rescue TypeError => e
113
- # Handle test scenario where an_rpc.call is a mock object
114
92
  raise unless e.message.include?('Core::Call')
115
93
 
116
- # In test environment, just log the response
117
94
  warn "RESOURCE_EXHAUSTED: #{RESOURCE_EXHAUSTED_MESSAGE}" if defined?(RSpec)
118
95
  end
119
96
  rescue StandardError => e
120
- if ::GRPC.logger.respond_to?(:error)
121
- ::GRPC.logger.error("Failed to send resource exhausted response: #{e.message}")
97
+ if defined?(Gruf) && Gruf.respond_to?(:logger)
98
+ Gruf.logger.error("Failed to send resource exhausted response: #{e.message}")
122
99
  else
123
100
  warn "Failed to send resource exhausted response: #{e.message}"
124
101
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gruf
4
4
  module Queue
5
- VERSION = '0.1.1'
5
+ VERSION = '0.1.3'
6
6
  end
7
7
  end
data/lib/gruf-queue.rb CHANGED
@@ -2,11 +2,8 @@
2
2
 
3
3
  require 'gruf'
4
4
  require 'gruf/queue/version'
5
- require 'gruf/queue/pool'
5
+ require 'gruf/queue/pool_enhancements'
6
6
  require 'gruf/queue/queued_rpc_server'
7
- require 'gruf/queue/configuration'
8
- require 'gruf/queue/server_factory'
9
- require 'gruf/queue/interceptors/connection_reset'
10
7
  require 'gruf/queue/plugin'
11
8
 
12
9
  # Auto-install plugin when required unless explicitly disabled
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gruf-queue
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ether Moon
@@ -135,12 +135,9 @@ files:
135
135
  - README.md
136
136
  - gruf-queue.gemspec
137
137
  - lib/gruf-queue.rb
138
- - lib/gruf/queue/configuration.rb
139
- - lib/gruf/queue/interceptors/connection_reset.rb
140
138
  - lib/gruf/queue/plugin.rb
141
- - lib/gruf/queue/pool.rb
139
+ - lib/gruf/queue/pool_enhancements.rb
142
140
  - lib/gruf/queue/queued_rpc_server.rb
143
- - lib/gruf/queue/server_factory.rb
144
141
  - lib/gruf/queue/version.rb
145
142
  homepage: https://github.com/ether-moon/gruf-queue
146
143
  licenses:
@@ -1,107 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gruf
4
- module Queue
5
- # Configuration management for gruf-queue integration.
6
- #
7
- # Enhances Gruf configuration with queue-specific settings and provides
8
- # idempotent configuration setup with proper error handling.
9
- #
10
- # @example Configure Gruf
11
- # Gruf::Queue::Configuration.configure
12
- module Configuration
13
- module_function
14
-
15
- # Apply queue-specific configuration to Gruf.
16
- #
17
- # @return [void]
18
- # @raise [StandardError] if configuration fails
19
- def configure
20
- return if configured?
21
-
22
- begin
23
- Gruf.configure do |config|
24
- enhance_gruf_configuration(config)
25
- end
26
-
27
- @configured = true
28
- log_debug('Gruf configuration enhanced with rpc_server support', version: Gruf::Queue::VERSION)
29
- rescue StandardError => e
30
- log_error('Failed to enhance Gruf configuration', error: e.message, error_class: e.class.name)
31
- raise
32
- end
33
- end
34
-
35
- # Check if configuration has been applied.
36
- #
37
- # @return [Boolean] true if configured, false otherwise
38
- def configured?
39
- !!@configured
40
- end
41
-
42
- # Reset configuration state for testing.
43
- #
44
- # @return [void]
45
- # @raise [RuntimeError] if not in test environment
46
- def reset!
47
- raise 'reset! can only be called in test environments' unless test_environment?
48
-
49
- @configured = false
50
-
51
- # Reset Gruf configuration to clean state
52
- if defined?(Gruf) && Gruf.respond_to?(:configuration)
53
- config = Gruf.configuration
54
- config.rpc_server = nil if config.respond_to?(:rpc_server=)
55
- end
56
-
57
- log_debug('Gruf configuration state reset', reason: 'test_environment')
58
- end
59
-
60
- # Enhance Gruf configuration with rpc_server support
61
- def enhance_gruf_configuration(config)
62
- config.define_singleton_method(:rpc_server) { @rpc_server }
63
- config.define_singleton_method(:rpc_server=) { |val| @rpc_server = val }
64
- config.rpc_server = nil unless config.respond_to?(:rpc_server)
65
-
66
- # Make the configuration accessible via Gruf.configuration if not already available
67
- return if Gruf.respond_to?(:configuration)
68
-
69
- Gruf.define_singleton_method(:configuration) { config }
70
- end
71
-
72
- # Check if running in test environment
73
- def test_environment?
74
- ENV['RACK_ENV'] == 'test' ||
75
- ENV['RAILS_ENV'] == 'test' ||
76
- defined?(RSpec)
77
- end
78
-
79
- # Log debug message
80
- def log_debug(message, **metadata)
81
- return unless defined?(Gruf) && Gruf.respond_to?(:logger)
82
-
83
- if metadata.any?
84
- Gruf.logger.debug(metadata.merge(message: message))
85
- else
86
- Gruf.logger.debug(message)
87
- end
88
- rescue StandardError
89
- # Ignore logging errors
90
- end
91
-
92
- # Log error message
93
- def log_error(message, **metadata)
94
- return unless defined?(Gruf) && Gruf.respond_to?(:logger)
95
- return if test_environment? # Skip error logging in test environment
96
-
97
- if metadata.any?
98
- Gruf.logger.error(metadata.merge(message: message))
99
- else
100
- Gruf.logger.error(message)
101
- end
102
- rescue StandardError
103
- # Ignore logging errors
104
- end
105
- end
106
- end
107
- end
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gruf
4
- module Queue
5
- module Interceptors
6
- # ActiveRecord connection reset interceptor for threaded gRPC environments.
7
- #
8
- # Automatically resets database connections after each request to prevent
9
- # connection leaks and stale connections in multi-threaded environments.
10
- #
11
- # @example Basic usage with Gruf
12
- # config.interceptors.use(
13
- # Gruf::Queue::Interceptors::ConnectionReset,
14
- # target_classes: [ActiveRecord::Base]
15
- # )
16
- class ConnectionReset < ::Gruf::Interceptors::ServerInterceptor
17
- # Initialize interceptor with request context and options.
18
- #
19
- # @param request [Object] gRPC request object
20
- # @param call [Object] gRPC call object
21
- # @param method [Object] gRPC method object
22
- # @param options [Hash] Configuration options
23
- # @option options [Boolean] :enabled (true) Enable/disable interceptor
24
- # @option options [Array<Class>] :target_classes Classes to reset connections for
25
- def initialize(request, call, method, options = {})
26
- @request = request
27
- @call = call
28
- @method = method
29
- @options = options || {}
30
- # Set default enabled value if not provided
31
- @options[:enabled] = true unless @options.key?(:enabled)
32
- end
33
-
34
- # Execute request and reset connections on completion.
35
- #
36
- # @yield Block containing the actual request handling
37
- # @return [Object] Result of yielded block
38
- def call
39
- yield
40
- ensure
41
- begin
42
- reset_connections if enabled?
43
- rescue StandardError => e
44
- # Log error but don't prevent handler execution
45
- log_message(:error, "Connection reset failed: #{e.message}")
46
- end
47
- end
48
-
49
- private
50
-
51
- # Get interceptor configuration options.
52
- #
53
- # @return [Hash] Configuration options
54
- attr_reader :options
55
-
56
- # Check if interceptor is enabled and has valid target classes.
57
- #
58
- # @return [Boolean] true if should execute, false otherwise
59
- def enabled?
60
- # Check if explicitly disabled
61
- return false if options[:enabled] == false
62
-
63
- # If no target classes are provided and ActiveRecord is not available, disable
64
- return false if target_classes.empty?
65
-
66
- true
67
- end
68
-
69
- # Reset connections for all valid target classes.
70
- #
71
- # @return [void]
72
- def reset_connections
73
- validated_classes = validate_target_classes
74
- return if validated_classes.empty?
75
-
76
- reset_count = 0
77
- validated_classes.each do |klass|
78
- reset_count += 1 if reset_connection_for_class(klass)
79
- end
80
-
81
- log_reset_summary(reset_count, validated_classes.size) if reset_count.positive?
82
- end
83
-
84
- def target_classes
85
- # Use instance variable if set (for testing)
86
- return Array(@target_classes).compact if instance_variable_defined?(:@target_classes)
87
-
88
- # Otherwise use options or default
89
- default_classes = defined?(::ActiveRecord::Base) ? [::ActiveRecord::Base] : []
90
- classes = options.fetch(:target_classes, default_classes)
91
- Array(classes).compact
92
- end
93
-
94
- # Validate and filter target classes to ensure they support connection handling
95
- def validate_target_classes
96
- target_classes.select do |klass|
97
- valid_class?(klass)
98
- end
99
- end
100
-
101
- # Check if a class is valid for connection reset capability
102
- def valid_class?(klass)
103
- unless klass.is_a?(Class)
104
- log_validation_warning(klass, 'not a class')
105
- return false
106
- end
107
-
108
- unless klass.respond_to?(:connection_handler)
109
- log_validation_warning(klass, 'does not respond to connection_handler')
110
- return false
111
- end
112
-
113
- true
114
- end
115
-
116
- # Reset connections for a single class
117
- def reset_connection_for_class(klass)
118
- handler = klass.connection_handler
119
- handler.clear_active_connections!(:all) if handler.respond_to?(:clear_active_connections!)
120
- handler.clear_reloadable_connections! if handler.respond_to?(:clear_reloadable_connections!)
121
- true
122
- rescue StandardError => e
123
- log_reset_error(klass, e)
124
- false
125
- end
126
-
127
- # Log validation warnings
128
- def log_validation_warning(klass, reason)
129
- message = "Skipping connection reset for #{klass.inspect}: #{reason}"
130
- log_message(:debug, message)
131
- end
132
-
133
- # Log connection reset errors
134
- def log_reset_error(klass, error)
135
- message = "Failed to reset connections for #{klass}: #{error.message}"
136
- log_message(:warn, message)
137
- end
138
-
139
- # Log reset operation summary
140
- def log_reset_summary(reset_count, total_count)
141
- message = "Reset connections for #{reset_count}/#{total_count} classes"
142
- log_message(:debug, message)
143
- end
144
-
145
- # Centralized logging with fallback chain
146
- def log_message(level, message)
147
- if defined?(Rails) && Rails.logger.respond_to?(level)
148
- Rails.logger.public_send(level, message)
149
- elsif defined?(GRPC) && GRPC.logger.respond_to?(level)
150
- GRPC.logger.public_send(level, message)
151
- elsif level == :warn
152
- # Fallback to stderr for critical messages
153
- warn(message)
154
- end
155
- end
156
- end
157
- end
158
- end
159
- end
@@ -1,156 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gruf
4
- module Queue
5
- # Enhanced thread pool with structured logging and improved error handling.
6
- #
7
- # Extends GRPC::Pool to provide better job scheduling, thread safety,
8
- # and comprehensive error isolation for gRPC server request handling.
9
- #
10
- # @example Basic usage
11
- # pool = Gruf::Queue::Pool.new(10, keep_alive: 300)
12
- # pool.schedule { puts "Hello from worker thread" }
13
- # pool.start
14
- class Pool < ::GRPC::Pool
15
- # Default keep-alive time for worker threads (in seconds)
16
- DEFAULT_KEEP_ALIVE = 600
17
-
18
- # Initialize pool with specified size and keep-alive time.
19
- #
20
- # @param size [Integer] Number of worker threads to maintain
21
- # @param keep_alive [Integer] Thread keep-alive time in seconds
22
- def initialize(size, keep_alive: DEFAULT_KEEP_ALIVE)
23
- super
24
- end
25
-
26
- # Get number of jobs currently waiting in queue.
27
- #
28
- # @return [Integer] Count of queued jobs
29
- def jobs_waiting
30
- @jobs&.size || 0
31
- end
32
-
33
- # Schedule block for execution on worker thread.
34
- #
35
- # @param args [Array] Arguments to pass to block
36
- # @param blk [Proc] Block to execute
37
- # @return [Boolean] true if scheduled, false if stopped or nil block
38
- def schedule(*args, &blk)
39
- return false if blk.nil?
40
-
41
- @stop_mutex.synchronize do
42
- if @stopped
43
- log_structured(:warn, 'Job scheduling rejected: pool already stopped')
44
- return false
45
- end
46
- log_structured(:debug, 'Job scheduled for execution')
47
-
48
- @jobs << [blk, args]
49
- true
50
- end
51
- rescue StandardError => e
52
- log_structured(:error, 'Failed to schedule job', error: e.message)
53
- false
54
- end
55
-
56
- # Start thread pool and create worker threads.
57
- #
58
- # @raise [RuntimeError] if pool is already stopped
59
- # @return [void]
60
- def start
61
- @stop_mutex.synchronize do
62
- raise 'Pool already stopped' if @stopped
63
- end
64
-
65
- target_size = @size.to_i
66
- log_structured(:info, 'Starting thread pool', target_size: target_size)
67
-
68
- until @workers.size == target_size
69
- next_thread = create_worker_thread
70
- @workers << next_thread if next_thread
71
- end
72
-
73
- log_structured(:info, 'Thread pool started', worker_count: @workers.size)
74
- rescue StandardError => e
75
- log_structured(:error, 'Failed to start thread pool', error: e.message)
76
- raise
77
- end
78
-
79
- protected
80
-
81
- # Create new worker thread with error handling.
82
- #
83
- # @return [Thread, nil] Worker thread or nil on error
84
- def create_worker_thread
85
- Thread.new do
86
- catch(:exit) do
87
- # allows { throw :exit } to kill a thread
88
- _loop_execute_jobs
89
- end
90
- remove_current_thread
91
- end
92
- rescue StandardError => e
93
- log_structured(:error, 'Failed to create worker thread', error: e.message)
94
- nil
95
- end
96
-
97
- # Main worker thread loop for job execution.
98
- #
99
- # @return [void]
100
- # @api private
101
- def _loop_execute_jobs
102
- Thread.current.name = "gruf-queue-worker-#{Thread.current.object_id}"
103
- log_structured(:debug, 'Worker thread started', thread_id: Thread.current.object_id)
104
-
105
- loop do
106
- begin
107
- blk, args = @jobs.pop
108
- execute_job_safely(blk, args)
109
- rescue StandardError => e
110
- log_structured(:error, 'Unexpected error in worker thread',
111
- error: e.message,
112
- backtrace: e.backtrace&.first(5))
113
- end
114
-
115
- @stop_mutex.synchronize do
116
- return if @stopped
117
- end
118
- end
119
- ensure
120
- log_structured(:debug, 'Worker thread stopping', thread_id: Thread.current.object_id)
121
- end
122
-
123
- # Execute job with error isolation and logging.
124
- #
125
- # @param blk [Proc] Job block to execute
126
- # @param args [Array] Arguments for the block
127
- # @return [void]
128
- def execute_job_safely(blk, args)
129
- blk.call(*args)
130
- rescue StandardError, GRPC::Core::CallError => e
131
- log_structured(:warn, 'Job execution failed',
132
- error: e.message,
133
- error_class: e.class.name)
134
- # Log full backtrace only in debug mode to avoid log spam
135
- log_structured(:debug, 'Job execution backtrace', backtrace: e.backtrace) if e.backtrace
136
- end
137
-
138
- # Log message with structured metadata.
139
- #
140
- # @param level [Symbol] Log level (:info, :warn, :error, :debug)
141
- # @param message [String] Base log message
142
- # @param metadata [Hash] Additional metadata to include
143
- # @return [void]
144
- def log_structured(level, message, **metadata)
145
- return unless GRPC.logger.respond_to?(level)
146
-
147
- if metadata.any?
148
- formatted_message = "#{message} #{metadata.map { |k, v| "#{k}=#{v}" }.join(' ')}"
149
- GRPC.logger.public_send(level, formatted_message)
150
- else
151
- GRPC.logger.public_send(level, message)
152
- end
153
- end
154
- end
155
- end
156
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Gruf
4
- module Queue
5
- # Factory for creating configured gRPC servers with queue-specific options.
6
- #
7
- # Provides centralized server creation with intelligent defaults, option validation,
8
- # and fallback handling for different server types.
9
- #
10
- # @example Create basic server
11
- # server = Gruf::Queue::ServerFactory.create_server(pool_size: 20)
12
- #
13
- # @example Create with custom server class
14
- # server = Gruf::Queue::ServerFactory.create_server(
15
- # server: MyCustomServer,
16
- # pool_size: 15
17
- # )
18
- module ServerFactory
19
- module_function
20
-
21
- # Create a new gRPC server instance with the provided options.
22
- #
23
- # @param opts [Hash] Configuration options for the server
24
- # @option opts [Class] :server Custom server class to use
25
- # @option opts [Integer] :pool_size Thread pool size
26
- # @option opts [Proc] :event_listener_proc Event listener procedure
27
- # @return [GRPC::RpcServer] Configured server instance
28
- def create_server(opts = {})
29
- # Default to QueuedRpcServer if no server specified
30
- rpc_server = opts.fetch(:server, nil)
31
- rpc_server ||= (defined?(Gruf) && Gruf.respond_to?(:rpc_server) && Gruf.rpc_server) || QueuedRpcServer
32
-
33
- # Validate server class
34
- validate_server_class!(rpc_server) if opts.key?(:server)
35
-
36
- default_options = get_default_options
37
-
38
- server_options = {
39
- pool_size: opts.fetch(:pool_size, default_options[:pool_size]),
40
- max_waiting_requests: opts.fetch(:max_waiting_requests, default_options[:max_waiting_requests]),
41
- poll_period: opts.fetch(:poll_period, default_options[:poll_period]),
42
- pool_keep_alive: opts.fetch(:pool_keep_alive, default_options[:pool_keep_alive]),
43
- connect_md_proc: opts.fetch(:connect_md_proc, nil),
44
- server_args: opts.fetch(:server_args, default_options[:server_args]),
45
- }
46
-
47
- # Handle interceptors via Gruf configuration if available
48
- interceptors = opts.fetch(:interceptors, [])
49
- if interceptors.any? && defined?(Gruf) && Gruf.respond_to?(:configuration)
50
- interceptors.each do |interceptor|
51
- Gruf.configuration.interceptors.use(interceptor, enabled: true)
52
- end
53
- elsif interceptors.any?
54
- server_options[:interceptors] = interceptors
55
- end
56
-
57
- begin
58
- rpc_server.new(**server_options)
59
- rescue ArgumentError => e
60
- raise unless e.message.include?('unknown keywords')
61
-
62
- # Filter out unsupported options for basic GRPC::RpcServer
63
- filtered_options = server_options.except(:pool_size, :max_waiting_requests, :poll_period, :pool_keep_alive)
64
- rpc_server.new(**filtered_options)
65
- end
66
- end
67
-
68
- # Validate server class inherits from GRPC::RpcServer.
69
- #
70
- # @param server_class [Class] Server class to validate
71
- # @raise [ArgumentError] if not a valid GRPC server class
72
- # @return [void]
73
- def validate_server_class!(server_class)
74
- return if server_class.is_a?(Class) && server_class <= ::GRPC::RpcServer
75
-
76
- raise ArgumentError, "Server must be a subclass of GRPC::RpcServer, got #{server_class}"
77
- end
78
- module_function :validate_server_class!
79
-
80
- # Get default server options with Gruf integration fallback.
81
- #
82
- # @return [Hash] Default configuration options
83
- def get_default_options
84
- if defined?(Gruf) && Gruf.respond_to?(:rpc_server_options)
85
- Gruf.rpc_server_options
86
- else
87
- {
88
- pool_size: QueuedRpcServer::DEFAULT_POOL_SIZE,
89
- max_waiting_requests: QueuedRpcServer::DEFAULT_MAX_WAITING_REQUESTS,
90
- poll_period: QueuedRpcServer::DEFAULT_POLL_PERIOD,
91
- pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
92
- server_args: {},
93
- }
94
- end
95
- end
96
- module_function :get_default_options
97
- end
98
- end
99
- end