gruf-queue 0.1.2 → 0.1.4
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 +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +18 -276
- data/lib/gruf/queue/plugin.rb +4 -72
- data/lib/gruf/queue/pool_enhancements.rb +24 -14
- data/lib/gruf/queue/version.rb +1 -1
- data/lib/gruf-queue.rb +1 -5
- metadata +1 -6
- data/lib/gruf/queue/configuration.rb +0 -64
- data/lib/gruf/queue/interceptors/connection_reset.rb +0 -87
- data/lib/gruf/queue/pool.rb +0 -89
- data/lib/gruf/queue/queued_rpc_server.rb +0 -105
- data/lib/gruf/queue/server_factory.rb +0 -99
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc1f1ae6ceeae244f3240289293550ac50d4817c146bdd0ae08c0ba4aac3d6b9
|
4
|
+
data.tar.gz: 510388ca36e935b14f7d727acc5f74a46969018b87cce11bfedf6a0a5723ba1b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 456159e1ad576446eb160c81a5fd285a8892c005cafc89c3607fcf8371da9e65e98481f14d44760f798cba36abda25e55c80199779e5e68339b6836faf5a32b3
|
7
|
+
data.tar.gz: 2c4e4d90a9667a860de5fb243d0d1c8d4fa8cae4499307c5f65f9e2b0dc7827973662ea24b03c1d1b000807a880645c272b93a52c8b4cd373c01e96c765028a7
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,56 @@ 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.4]
|
9
|
+
|
10
|
+
### Changed
|
11
|
+
- Renamed `AvaiallableQueue` to `AlwaysReadyQueue` (fixed typo)
|
12
|
+
- Improved null object pattern implementation for worker queue management
|
13
|
+
- Simplified README.md to focus on core functionality
|
14
|
+
|
15
|
+
### Added
|
16
|
+
- Graceful shutdown support with `@stopped` flag checking
|
17
|
+
- Enhanced test coverage for environment variable configuration
|
18
|
+
- Comprehensive test suite for graceful shutdown behavior
|
19
|
+
- Thread-safe shutdown handling for GRPC::Pool enhancements
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Fixed graceful shutdown issues that prevented proper signal handling
|
23
|
+
- Improved AlwaysReadyQueue method implementations for better compatibility
|
24
|
+
- Enhanced error handling in ready_for_work? method
|
25
|
+
|
26
|
+
### Improved
|
27
|
+
- More accurate documentation reflecting actual gem purpose
|
28
|
+
- Better test organization with focused integration tests
|
29
|
+
- Cleaner code structure with proper separation of concerns
|
30
|
+
|
31
|
+
## [0.1.3]
|
32
|
+
|
33
|
+
### Changed
|
34
|
+
- **BREAKING**: Removed ServerFactory - use `QueuedRpcServer.new()` directly instead
|
35
|
+
- **BREAKING**: Removed Configuration wrapper - use Gruf's native configuration
|
36
|
+
- **BREAKING**: Removed custom ConnectionReset interceptor - use gruf's built-in `Gruf::Interceptors::ActiveRecord::ConnectionReset`
|
37
|
+
- Simplified plugin.rb from 104 to 60 lines by removing complex validation logic
|
38
|
+
- Merged pool.rb into pool_enhancements.rb for better organization
|
39
|
+
|
40
|
+
### Removed
|
41
|
+
- `Gruf::Queue::ServerFactory` - unnecessary abstraction layer
|
42
|
+
- `Gruf::Queue::Configuration` - wrapper around Gruf's native config
|
43
|
+
- `Gruf::Queue::Interceptors::ConnectionReset` - replaced with gruf's built-in
|
44
|
+
- `Gruf::Queue::Pool` class - functionality moved to PoolEnhancements module
|
45
|
+
- Obsolete test files and redundant test cases
|
46
|
+
|
47
|
+
### Improved
|
48
|
+
- **730 lines of code removed** - dramatically simplified codebase
|
49
|
+
- More direct and intuitive API without unnecessary abstractions
|
50
|
+
- Better integration with gruf's native features
|
51
|
+
- Cleaner test suite with eliminated logging noise
|
52
|
+
- Improved maintainability and reduced complexity
|
53
|
+
|
54
|
+
### Fixed
|
55
|
+
- Suppressed Gruf logger output during tests for cleaner test runs
|
56
|
+
- Updated all documentation and examples to reflect simplified API
|
57
|
+
|
8
58
|
## [0.1.2]
|
9
59
|
|
10
60
|
### Changed
|
data/README.md
CHANGED
@@ -3,291 +3,55 @@
|
|
3
3
|
[](https://badge.fury.io/rb/gruf-queue)
|
4
4
|
[](https://ruby-lang.org)
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
6
|
-
[](https://github.com/your-org/gruf-queue/actions)
|
7
6
|
|
8
|
-
|
7
|
+
Simple GRPC::Pool enhancement that replaces worker-based capacity checking with job count based threshold.
|
9
8
|
|
10
|
-
##
|
9
|
+
## What it does
|
11
10
|
|
12
|
-
|
13
|
-
-
|
14
|
-
-
|
15
|
-
-
|
16
|
-
|
17
|
-
🔌 **Plugin Architecture**
|
18
|
-
- Zero-configuration auto-installation
|
19
|
-
- Non-invasive integration with existing Gruf applications
|
20
|
-
- Modular design with configurable components
|
21
|
-
|
22
|
-
🛡️ **Reliability & Safety**
|
23
|
-
- Thread-safe operations with proper synchronization
|
24
|
-
- Graceful error handling and recovery
|
25
|
-
- Smart ActiveRecord connection management
|
26
|
-
|
27
|
-
📊 **Observability**
|
28
|
-
- Structured logging with metadata for monitoring
|
29
|
-
- Thread naming for better debugging
|
30
|
-
- Comprehensive error reporting with context
|
11
|
+
- Replaces `GRPC::Pool` worker queue management with a simple job count check
|
12
|
+
- Uses `jobs_waiting < threshold` instead of tracking actual worker availability
|
13
|
+
- Configurable threshold via `GRUF_MAX_WAITING_REQUESTS` (default: 60)
|
14
|
+
- Supports graceful shutdown
|
31
15
|
|
32
16
|
## Installation
|
33
17
|
|
34
|
-
Add this line to your application's Gemfile:
|
35
|
-
|
36
18
|
```ruby
|
37
19
|
gem 'gruf-queue'
|
38
20
|
```
|
39
21
|
|
40
|
-
|
41
|
-
|
42
|
-
```bash
|
43
|
-
$ bundle install
|
44
|
-
```
|
45
|
-
|
46
|
-
## Quick Start
|
47
|
-
|
48
|
-
### Zero Configuration Setup
|
49
|
-
|
50
|
-
The simplest way to use gruf-queue is to just require it:
|
22
|
+
## Usage
|
51
23
|
|
52
24
|
```ruby
|
53
25
|
require 'gruf-queue'
|
54
26
|
|
55
|
-
#
|
56
|
-
|
27
|
+
# Optional: Set threshold (default is 60)
|
28
|
+
ENV['GRUF_MAX_WAITING_REQUESTS'] = '100'
|
29
|
+
|
30
|
+
# Start server normally
|
57
31
|
Gruf::Server.new(
|
58
32
|
hostname: '0.0.0.0:9001',
|
59
33
|
services: [YourService]
|
60
34
|
).start
|
61
35
|
```
|
62
36
|
|
63
|
-
|
64
|
-
- ✅ Configures Gruf to use `QueuedRpcServer`
|
65
|
-
- ✅ Registers the `ConnectionReset` interceptor (if ActiveRecord is available)
|
66
|
-
- ✅ Sets up enhanced thread pool management
|
67
|
-
- ✅ Enables structured logging
|
68
|
-
|
69
|
-
### Manual Installation Control
|
70
|
-
|
71
|
-
To disable auto-installation and configure manually:
|
37
|
+
## Configuration
|
72
38
|
|
73
39
|
```ruby
|
74
40
|
# Disable auto-installation
|
75
41
|
ENV['GRUF_QUEUE_NO_AUTO_INSTALL'] = 'true'
|
76
42
|
|
77
|
-
require 'gruf-queue'
|
78
|
-
|
79
43
|
# Manual installation
|
80
44
|
Gruf::Queue::Plugin.install!
|
81
|
-
|
82
|
-
# Or configure components individually
|
83
|
-
Gruf.configure do |config|
|
84
|
-
config.rpc_server = Gruf::Queue::QueuedRpcServer
|
85
|
-
|
86
|
-
config.interceptors.use(
|
87
|
-
Gruf::Queue::Interceptors::ConnectionReset,
|
88
|
-
enabled: true,
|
89
|
-
target_classes: [ActiveRecord::Base]
|
90
|
-
)
|
91
|
-
end
|
92
|
-
```
|
93
|
-
|
94
|
-
## Configuration
|
95
|
-
|
96
|
-
### Server Configuration
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
# Configure the QueuedRpcServer with custom settings
|
100
|
-
Gruf.configure do |config|
|
101
|
-
config.rpc_server = Gruf::Queue::QueuedRpcServer
|
102
|
-
config.rpc_server_options = {
|
103
|
-
pool_size: 10, # Thread pool size
|
104
|
-
max_waiting_requests: 20, # Queue capacity
|
105
|
-
pool_keep_alive: 300, # Thread keep-alive time
|
106
|
-
poll_period: 1 # Polling interval
|
107
|
-
}
|
108
|
-
end
|
109
45
|
```
|
110
46
|
|
111
|
-
|
112
|
-
|
113
|
-
For advanced server creation:
|
47
|
+
## How it works
|
114
48
|
|
115
49
|
```ruby
|
116
|
-
#
|
117
|
-
|
118
|
-
pool_size: 15,
|
119
|
-
max_waiting_requests: 30,
|
120
|
-
server: Gruf::Queue::QueuedRpcServer,
|
121
|
-
interceptors: [
|
122
|
-
Gruf::Queue::Interceptors::ConnectionReset
|
123
|
-
]
|
124
|
-
)
|
50
|
+
# Before: GRPC::Pool tracks actual worker availability
|
51
|
+
pool.ready_for_work? # Complex worker state checking
|
125
52
|
|
126
|
-
|
127
|
-
|
128
|
-
services: [YourService],
|
129
|
-
rpc_server: server
|
130
|
-
).start
|
131
|
-
```
|
132
|
-
|
133
|
-
### ActiveRecord Connection Management
|
134
|
-
|
135
|
-
The `ConnectionReset` interceptor automatically manages database connections:
|
136
|
-
|
137
|
-
```ruby
|
138
|
-
# Automatically registered when ActiveRecord is detected
|
139
|
-
# Customizable with additional target classes:
|
140
|
-
Gruf.configure do |config|
|
141
|
-
config.interceptors.use(
|
142
|
-
Gruf::Queue::Interceptors::ConnectionReset,
|
143
|
-
enabled: true,
|
144
|
-
target_classes: [ActiveRecord::Base, CustomConnectionClass]
|
145
|
-
)
|
146
|
-
end
|
147
|
-
```
|
148
|
-
|
149
|
-
## Architecture
|
150
|
-
|
151
|
-
### Core Components
|
152
|
-
|
153
|
-
#### `Gruf::Queue::QueuedRpcServer`
|
154
|
-
Enhanced gRPC server with intelligent resource management:
|
155
|
-
- Monitors thread pool capacity
|
156
|
-
- Automatically sends `RESOURCE_EXHAUSTED` when overloaded
|
157
|
-
- Provides structured error handling
|
158
|
-
|
159
|
-
#### `Gruf::Queue::Pool`
|
160
|
-
Advanced thread pool implementation:
|
161
|
-
- Extends `GRPC::Pool` with enhanced job scheduling
|
162
|
-
- Structured logging with thread identification
|
163
|
-
- Comprehensive error isolation and recovery
|
164
|
-
|
165
|
-
#### `Gruf::Queue::Interceptors::ConnectionReset`
|
166
|
-
Smart database connection management:
|
167
|
-
- Automatically resets ActiveRecord connections after each request
|
168
|
-
- Validates connection handlers before attempting reset
|
169
|
-
- Graceful error handling to prevent request failures
|
170
|
-
|
171
|
-
#### `Gruf::Queue::Plugin`
|
172
|
-
Plugin management system:
|
173
|
-
- Idempotent installation process
|
174
|
-
- Comprehensive error handling during setup
|
175
|
-
- Validation of dependencies and requirements
|
176
|
-
|
177
|
-
### Plugin Lifecycle
|
178
|
-
|
179
|
-
```ruby
|
180
|
-
# Plugin installation process
|
181
|
-
Gruf::Queue::Plugin.install! # => true (success) or false (failure)
|
182
|
-
|
183
|
-
# Check installation status
|
184
|
-
Gruf::Queue::Plugin.installed? # => true/false
|
185
|
-
|
186
|
-
# Reset for testing (test environments only)
|
187
|
-
Gruf::Queue::Plugin.reset!
|
188
|
-
```
|
189
|
-
|
190
|
-
## Advanced Usage
|
191
|
-
|
192
|
-
### Custom Server Implementation
|
193
|
-
|
194
|
-
```ruby
|
195
|
-
# Create a custom server class
|
196
|
-
class MyCustomServer < Gruf::Queue::QueuedRpcServer
|
197
|
-
def initialize(*)
|
198
|
-
super
|
199
|
-
# Custom initialization logic
|
200
|
-
end
|
201
|
-
|
202
|
-
private
|
203
|
-
|
204
|
-
def handle_custom_logic
|
205
|
-
# Your custom server logic
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
# Use with gruf-queue
|
210
|
-
Gruf.configure do |config|
|
211
|
-
config.rpc_server = MyCustomServer
|
212
|
-
end
|
213
|
-
```
|
214
|
-
|
215
|
-
### Monitoring and Observability
|
216
|
-
|
217
|
-
```ruby
|
218
|
-
# Structured logging is automatically enabled
|
219
|
-
# Logs include contextual information:
|
220
|
-
# - Thread IDs for debugging
|
221
|
-
# - Error details with full context
|
222
|
-
# - Performance metrics
|
223
|
-
# - Resource utilization info
|
224
|
-
|
225
|
-
# Example log output:
|
226
|
-
# [INFO] Starting thread pool target_size=10
|
227
|
-
# [DEBUG] Worker thread started thread_id=47185656920560
|
228
|
-
# [WARN] Job execution failed error=SomeError error_class=StandardError
|
229
|
-
```
|
230
|
-
|
231
|
-
### Environment-specific Configuration
|
232
|
-
|
233
|
-
```ruby
|
234
|
-
# Production settings
|
235
|
-
if Rails.env.production?
|
236
|
-
Gruf.configure do |config|
|
237
|
-
config.rpc_server_options = {
|
238
|
-
pool_size: 20,
|
239
|
-
max_waiting_requests: 50,
|
240
|
-
pool_keep_alive: 600
|
241
|
-
}
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
|
-
# Development settings
|
246
|
-
if Rails.env.development?
|
247
|
-
Gruf.configure do |config|
|
248
|
-
config.rpc_server_options = {
|
249
|
-
pool_size: 5,
|
250
|
-
max_waiting_requests: 10,
|
251
|
-
pool_keep_alive: 60
|
252
|
-
}
|
253
|
-
end
|
254
|
-
end
|
255
|
-
```
|
256
|
-
|
257
|
-
## Performance Considerations
|
258
|
-
|
259
|
-
- **Thread Pool Sizing**: Set `pool_size` based on your server's CPU cores and expected load
|
260
|
-
- **Queue Capacity**: Configure `max_waiting_requests` to handle traffic spikes
|
261
|
-
- **Connection Management**: The `ConnectionReset` interceptor prevents connection leaks in threaded environments
|
262
|
-
- **Resource Monitoring**: Use structured logs to monitor resource utilization
|
263
|
-
|
264
|
-
## Troubleshooting
|
265
|
-
|
266
|
-
### Common Issues
|
267
|
-
|
268
|
-
**Server not starting with custom configuration:**
|
269
|
-
```ruby
|
270
|
-
# Ensure plugin is installed before configuration
|
271
|
-
Gruf::Queue::Plugin.install!
|
272
|
-
# Then configure...
|
273
|
-
```
|
274
|
-
|
275
|
-
**ActiveRecord connection issues:**
|
276
|
-
```ruby
|
277
|
-
# Verify ActiveRecord is loaded before gruf-queue
|
278
|
-
require 'active_record'
|
279
|
-
require 'gruf-queue'
|
280
|
-
```
|
281
|
-
|
282
|
-
**High memory usage:**
|
283
|
-
```ruby
|
284
|
-
# Adjust pool size and keep-alive settings
|
285
|
-
Gruf.configure do |config|
|
286
|
-
config.rpc_server_options = {
|
287
|
-
pool_size: 8, # Reduce if memory constrained
|
288
|
-
pool_keep_alive: 60 # Shorter keep-alive
|
289
|
-
}
|
290
|
-
end
|
53
|
+
# After: Simple job count threshold
|
54
|
+
pool.ready_for_work? # jobs_waiting < max_waiting_requests
|
291
55
|
```
|
292
56
|
|
293
57
|
## Requirements
|
@@ -296,28 +60,6 @@ end
|
|
296
60
|
- Gruf >= 2.21.0
|
297
61
|
- gRPC >= 1.0
|
298
62
|
|
299
|
-
## Development
|
300
|
-
|
301
|
-
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
302
|
-
|
303
|
-
```bash
|
304
|
-
# Install dependencies
|
305
|
-
$ bundle install
|
306
|
-
|
307
|
-
# Run tests
|
308
|
-
$ bundle exec rspec
|
309
|
-
|
310
|
-
# Run linting
|
311
|
-
$ bundle exec rubocop
|
312
|
-
|
313
|
-
# Install locally
|
314
|
-
$ bundle exec rake install
|
315
|
-
```
|
316
|
-
|
317
|
-
## Contributing
|
318
|
-
|
319
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/ether-moon/gruf-queue. This project is intended to be a safe, welcoming space for collaboration.
|
320
|
-
|
321
63
|
## License
|
322
64
|
|
323
|
-
|
65
|
+
MIT License
|
data/lib/gruf/queue/plugin.rb
CHANGED
@@ -6,92 +6,24 @@ module Gruf
|
|
6
6
|
module Queue
|
7
7
|
# Plugin management for gruf-queue integration.
|
8
8
|
#
|
9
|
-
# Provides
|
10
|
-
# including RPC server setup and interceptor registration.
|
11
|
-
#
|
12
|
-
# @example Install plugin
|
13
|
-
# Gruf::Queue::Plugin.install! # => true
|
14
|
-
#
|
15
|
-
# @example Check installation status
|
16
|
-
# Gruf::Queue::Plugin.installed? # => true
|
9
|
+
# Provides simple installation and configuration of queue-specific settings.
|
17
10
|
class Plugin
|
18
11
|
class << self
|
19
12
|
def install!
|
20
13
|
return false if @installed
|
21
14
|
|
22
|
-
|
23
|
-
Configuration.configure unless Configuration.configured?
|
24
|
-
enhance_grpc_pool
|
25
|
-
Gruf.configure do |config|
|
26
|
-
configure_rpc_server(config)
|
27
|
-
configure_interceptors(config)
|
28
|
-
end
|
15
|
+
enhance_grpc_pool
|
29
16
|
|
30
|
-
|
31
|
-
true
|
32
|
-
rescue StandardError
|
33
|
-
false
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def installed?
|
38
|
-
!!@installed
|
17
|
+
@installed = true
|
39
18
|
end
|
40
19
|
|
20
|
+
# Reset installation state for testing
|
41
21
|
def reset!
|
42
22
|
@installed = false
|
43
|
-
Configuration.reset! if Configuration.respond_to?(:reset!)
|
44
23
|
end
|
45
24
|
|
46
25
|
private
|
47
26
|
|
48
|
-
def configure_rpc_server(config)
|
49
|
-
config.rpc_server = QueuedRpcServer
|
50
|
-
end
|
51
|
-
|
52
|
-
def configure_interceptors(config)
|
53
|
-
return unless defined?(ActiveRecord)
|
54
|
-
|
55
|
-
validate_active_record_availability
|
56
|
-
ensure_interceptors_registry_available(config)
|
57
|
-
|
58
|
-
config.interceptors.use(
|
59
|
-
Interceptors::ConnectionReset,
|
60
|
-
enabled: true,
|
61
|
-
target_classes: [ActiveRecord::Base],
|
62
|
-
)
|
63
|
-
end
|
64
|
-
|
65
|
-
def validate_active_record_availability
|
66
|
-
return if ActiveRecord::Base.respond_to?(:connection_handler)
|
67
|
-
|
68
|
-
raise StandardError, 'ActiveRecord::Base does not support connection_handler'
|
69
|
-
end
|
70
|
-
|
71
|
-
def ensure_interceptors_registry_available(config)
|
72
|
-
return if config.respond_to?(:interceptors) && !config.interceptors.nil?
|
73
|
-
|
74
|
-
begin
|
75
|
-
require 'gruf/interceptors/registry'
|
76
|
-
rescue LoadError => e
|
77
|
-
raise StandardError, "Failed to load Gruf interceptors registry: #{e.message}"
|
78
|
-
end
|
79
|
-
|
80
|
-
if config.respond_to?(:interceptors=)
|
81
|
-
config.interceptors = ::Gruf::Interceptors::Registry.new
|
82
|
-
elsif config.respond_to?(:define_singleton_method)
|
83
|
-
registry = ::Gruf::Interceptors::Registry.new
|
84
|
-
config.define_singleton_method(:interceptors) { registry }
|
85
|
-
config.define_singleton_method(:interceptors=) { |val| registry = val }
|
86
|
-
else
|
87
|
-
raise StandardError, 'Cannot initialize interceptors registry: configuration object lacks required methods'
|
88
|
-
end
|
89
|
-
|
90
|
-
return if config.respond_to?(:interceptors) && config.interceptors.respond_to?(:use)
|
91
|
-
|
92
|
-
raise StandardError, 'Interceptors registry initialization failed: missing use method'
|
93
|
-
end
|
94
|
-
|
95
27
|
def enhance_grpc_pool
|
96
28
|
return if ::GRPC::Pool.included_modules.include?(Gruf::Queue::PoolEnhancements)
|
97
29
|
|
@@ -2,24 +2,34 @@
|
|
2
2
|
|
3
3
|
module Gruf
|
4
4
|
module Queue
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
# Null object implementation for worker queue management.
|
6
|
+
# Provides clean interface without complex worker state tracking.
|
7
|
+
class AlwaysReadyQueue
|
8
|
+
def empty? = false
|
9
|
+
|
10
|
+
def <<(*) = self
|
11
|
+
|
12
|
+
def push(*) = self
|
13
|
+
|
14
|
+
def pop = ::Queue.new
|
15
|
+
|
16
|
+
def size = 1
|
17
|
+
end
|
18
|
+
|
19
|
+
# Enhanced GRPC::Pool with job count based capacity management
|
12
20
|
module PoolEnhancements
|
13
|
-
|
14
|
-
|
21
|
+
DEFAULT_MAX_WAITING_REQUESTS = 60
|
22
|
+
|
23
|
+
def initialize(...)
|
24
|
+
super
|
25
|
+
@ready_workers = AlwaysReadyQueue.new
|
26
|
+
@max_waiting_requests = ENV.fetch('GRUF_MAX_WAITING_REQUESTS', DEFAULT_MAX_WAITING_REQUESTS).to_i
|
15
27
|
end
|
16
28
|
|
17
|
-
def
|
18
|
-
return false if
|
29
|
+
def ready_for_work?
|
30
|
+
return false if @stopped
|
19
31
|
|
20
|
-
|
21
|
-
rescue StandardError
|
22
|
-
false
|
32
|
+
jobs_waiting < @max_waiting_requests
|
23
33
|
end
|
24
34
|
end
|
25
35
|
end
|
data/lib/gruf/queue/version.rb
CHANGED
data/lib/gruf-queue.rb
CHANGED
@@ -2,11 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'gruf'
|
4
4
|
require 'gruf/queue/version'
|
5
|
-
require 'gruf/queue/
|
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'
|
5
|
+
require 'gruf/queue/pool_enhancements'
|
10
6
|
require 'gruf/queue/plugin'
|
11
7
|
|
12
8
|
# 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.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ether Moon
|
@@ -135,13 +135,8 @@ 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
|
142
139
|
- lib/gruf/queue/pool_enhancements.rb
|
143
|
-
- lib/gruf/queue/queued_rpc_server.rb
|
144
|
-
- lib/gruf/queue/server_factory.rb
|
145
140
|
- lib/gruf/queue/version.rb
|
146
141
|
homepage: https://github.com/ether-moon/gruf-queue
|
147
142
|
licenses:
|
@@ -1,64 +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
|
-
def configure
|
16
|
-
return if configured?
|
17
|
-
|
18
|
-
begin
|
19
|
-
Gruf.configure do |config|
|
20
|
-
enhance_gruf_configuration(config)
|
21
|
-
end
|
22
|
-
|
23
|
-
@configured = true
|
24
|
-
rescue StandardError => e
|
25
|
-
if defined?(Gruf) && Gruf.respond_to?(:logger)
|
26
|
-
Gruf.logger.error("Failed to enhance Gruf configuration: #{e.message}")
|
27
|
-
end
|
28
|
-
raise
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def configured?
|
33
|
-
!!@configured
|
34
|
-
end
|
35
|
-
|
36
|
-
def reset!
|
37
|
-
raise 'reset! can only be called in test environments' unless test_environment?
|
38
|
-
|
39
|
-
@configured = false
|
40
|
-
|
41
|
-
return unless defined?(Gruf) && Gruf.respond_to?(:configuration)
|
42
|
-
|
43
|
-
config = Gruf.configuration
|
44
|
-
config.rpc_server = nil if config.respond_to?(:rpc_server=)
|
45
|
-
end
|
46
|
-
|
47
|
-
def enhance_gruf_configuration(config)
|
48
|
-
config.define_singleton_method(:rpc_server) { @rpc_server }
|
49
|
-
config.define_singleton_method(:rpc_server=) { |val| @rpc_server = val }
|
50
|
-
config.rpc_server = nil unless config.respond_to?(:rpc_server)
|
51
|
-
|
52
|
-
return if Gruf.respond_to?(:configuration)
|
53
|
-
|
54
|
-
Gruf.define_singleton_method(:configuration) { config }
|
55
|
-
end
|
56
|
-
|
57
|
-
def test_environment?
|
58
|
-
ENV['RACK_ENV'] == 'test' ||
|
59
|
-
ENV['RAILS_ENV'] == 'test' ||
|
60
|
-
defined?(RSpec)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,87 +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
|
-
def initialize(request, call, method, options = {})
|
18
|
-
@request = request
|
19
|
-
@call = call
|
20
|
-
@method = method
|
21
|
-
@options = options || {}
|
22
|
-
@options[:enabled] = true unless @options.key?(:enabled)
|
23
|
-
end
|
24
|
-
|
25
|
-
def call
|
26
|
-
yield
|
27
|
-
ensure
|
28
|
-
begin
|
29
|
-
reset_connections if enabled?
|
30
|
-
rescue StandardError
|
31
|
-
# Ignore connection reset errors silently
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
attr_reader :options
|
38
|
-
|
39
|
-
def enabled?
|
40
|
-
return false if options[:enabled] == false
|
41
|
-
return false if target_classes.empty?
|
42
|
-
|
43
|
-
true
|
44
|
-
end
|
45
|
-
|
46
|
-
def reset_connections
|
47
|
-
validated_classes = validate_target_classes
|
48
|
-
return if validated_classes.empty?
|
49
|
-
|
50
|
-
validated_classes.each do |klass|
|
51
|
-
reset_connection_for_class(klass)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def target_classes
|
56
|
-
return Array(@target_classes).compact if instance_variable_defined?(:@target_classes)
|
57
|
-
|
58
|
-
default_classes = defined?(::ActiveRecord::Base) ? [::ActiveRecord::Base] : []
|
59
|
-
classes = options.fetch(:target_classes, default_classes)
|
60
|
-
Array(classes).compact
|
61
|
-
end
|
62
|
-
|
63
|
-
def validate_target_classes
|
64
|
-
target_classes.select do |klass|
|
65
|
-
valid_class?(klass)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def valid_class?(klass)
|
70
|
-
return false unless klass.is_a?(Class)
|
71
|
-
return false unless klass.respond_to?(:connection_handler)
|
72
|
-
|
73
|
-
true
|
74
|
-
end
|
75
|
-
|
76
|
-
def reset_connection_for_class(klass)
|
77
|
-
handler = klass.connection_handler
|
78
|
-
handler.clear_active_connections!(:all) if handler.respond_to?(:clear_active_connections!)
|
79
|
-
handler.clear_reloadable_connections! if handler.respond_to?(:clear_reloadable_connections!)
|
80
|
-
true
|
81
|
-
rescue StandardError
|
82
|
-
false
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
data/lib/gruf/queue/pool.rb
DELETED
@@ -1,89 +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
|
-
def initialize(size, keep_alive: DEFAULT_KEEP_ALIVE)
|
19
|
-
super
|
20
|
-
end
|
21
|
-
|
22
|
-
def jobs_waiting
|
23
|
-
@jobs&.size || 0
|
24
|
-
end
|
25
|
-
|
26
|
-
def schedule(*args, &blk)
|
27
|
-
return false if blk.nil?
|
28
|
-
|
29
|
-
@stop_mutex.synchronize do
|
30
|
-
return false if @stopped
|
31
|
-
|
32
|
-
@jobs << [blk, args]
|
33
|
-
true
|
34
|
-
end
|
35
|
-
rescue StandardError
|
36
|
-
false
|
37
|
-
end
|
38
|
-
|
39
|
-
def start
|
40
|
-
@stop_mutex.synchronize do
|
41
|
-
raise 'Pool already stopped' if @stopped
|
42
|
-
end
|
43
|
-
|
44
|
-
target_size = @size.to_i
|
45
|
-
|
46
|
-
until @workers.size == target_size
|
47
|
-
next_thread = create_worker_thread
|
48
|
-
@workers << next_thread if next_thread
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
protected
|
53
|
-
|
54
|
-
def create_worker_thread
|
55
|
-
Thread.new do
|
56
|
-
catch(:exit) do
|
57
|
-
_loop_execute_jobs
|
58
|
-
end
|
59
|
-
remove_current_thread
|
60
|
-
end
|
61
|
-
rescue StandardError
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
|
65
|
-
def _loop_execute_jobs
|
66
|
-
Thread.current.name = "gruf-queue-worker-#{Thread.current.object_id}"
|
67
|
-
|
68
|
-
loop do
|
69
|
-
begin
|
70
|
-
blk, args = @jobs.pop
|
71
|
-
execute_job_safely(blk, args)
|
72
|
-
rescue ThreadError, SystemStackError, IOError
|
73
|
-
# Ignore system-level errors to prevent worker thread death
|
74
|
-
end
|
75
|
-
|
76
|
-
@stop_mutex.synchronize do
|
77
|
-
return if @stopped
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def execute_job_safely(blk, args)
|
83
|
-
blk.call(*args)
|
84
|
-
rescue SystemStackError, IOError, GRPC::Core::CallError, GRPC::BadStatus, Timeout::Error
|
85
|
-
# Ignore system/network/gRPC errors to prevent worker thread death
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
@@ -1,105 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'pool'
|
4
|
-
|
5
|
-
module Gruf
|
6
|
-
module Queue
|
7
|
-
# Enhanced gRPC server with queue-based request handling and resource exhaustion protection.
|
8
|
-
#
|
9
|
-
# Extends GRPC::RpcServer to provide intelligent thread pool management with
|
10
|
-
# automatic RESOURCE_EXHAUSTED responses when the server is overloaded.
|
11
|
-
#
|
12
|
-
# @example Basic usage
|
13
|
-
# server = Gruf::Queue::QueuedRpcServer.new(pool_size: 20)
|
14
|
-
# server.handle(MyService)
|
15
|
-
# server.run_till_terminated
|
16
|
-
class QueuedRpcServer < ::GRPC::RpcServer
|
17
|
-
# Default pool size for the thread pool
|
18
|
-
DEFAULT_POOL_SIZE = 30
|
19
|
-
|
20
|
-
# Default maximum number of waiting requests
|
21
|
-
DEFAULT_MAX_WAITING_REQUESTS = 60
|
22
|
-
|
23
|
-
# Default poll period for the server
|
24
|
-
DEFAULT_POLL_PERIOD = 1
|
25
|
-
|
26
|
-
# No-op procedure for gRPC active call creation
|
27
|
-
NOOP_PROC = proc { |x| x }.freeze
|
28
|
-
private_constant :NOOP_PROC
|
29
|
-
|
30
|
-
# Error message for resource exhaustion
|
31
|
-
RESOURCE_EXHAUSTED_MESSAGE = 'No free threads in thread pool'
|
32
|
-
|
33
|
-
def initialize(pool_size: DEFAULT_POOL_SIZE,
|
34
|
-
max_waiting_requests: DEFAULT_MAX_WAITING_REQUESTS,
|
35
|
-
pool_keep_alive: Pool::DEFAULT_KEEP_ALIVE,
|
36
|
-
poll_period: DEFAULT_POLL_PERIOD,
|
37
|
-
**args)
|
38
|
-
super
|
39
|
-
|
40
|
-
@pool_size = pool_size
|
41
|
-
@max_waiting_requests = max_waiting_requests
|
42
|
-
@pool_keep_alive = pool_keep_alive
|
43
|
-
@poll_period = poll_period
|
44
|
-
end
|
45
|
-
|
46
|
-
def available?(an_rpc)
|
47
|
-
job_count = safe_job_count
|
48
|
-
return an_rpc if job_count < @max_waiting_requests
|
49
|
-
|
50
|
-
send_resource_exhausted_response(an_rpc)
|
51
|
-
false
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def safe_job_count
|
57
|
-
return 0 unless @pool
|
58
|
-
|
59
|
-
@pool.jobs_waiting
|
60
|
-
rescue StandardError => e
|
61
|
-
Gruf.logger.warn("Failed to get job count: #{e.message}") if defined?(Gruf) && Gruf.respond_to?(:logger)
|
62
|
-
@max_waiting_requests
|
63
|
-
end
|
64
|
-
|
65
|
-
def send_resource_exhausted_response(an_rpc)
|
66
|
-
Gruf.logger.warn('no free worker threads currently') if defined?(Gruf) && Gruf.respond_to?(:logger)
|
67
|
-
|
68
|
-
begin
|
69
|
-
if an_rpc.respond_to?(:send_status)
|
70
|
-
an_rpc.send_status(
|
71
|
-
::GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
|
72
|
-
RESOURCE_EXHAUSTED_MESSAGE,
|
73
|
-
{},
|
74
|
-
)
|
75
|
-
return
|
76
|
-
end
|
77
|
-
|
78
|
-
active_call = ::GRPC::ActiveCall.new(
|
79
|
-
an_rpc.call,
|
80
|
-
NOOP_PROC,
|
81
|
-
NOOP_PROC,
|
82
|
-
an_rpc.deadline,
|
83
|
-
metadata_received: true,
|
84
|
-
started: false,
|
85
|
-
)
|
86
|
-
|
87
|
-
active_call.send_status(
|
88
|
-
::GRPC::Core::StatusCodes::RESOURCE_EXHAUSTED,
|
89
|
-
RESOURCE_EXHAUSTED_MESSAGE,
|
90
|
-
)
|
91
|
-
rescue TypeError => e
|
92
|
-
raise unless e.message.include?('Core::Call')
|
93
|
-
|
94
|
-
warn "RESOURCE_EXHAUSTED: #{RESOURCE_EXHAUSTED_MESSAGE}" if defined?(RSpec)
|
95
|
-
end
|
96
|
-
rescue StandardError => e
|
97
|
-
if defined?(Gruf) && Gruf.respond_to?(:logger)
|
98
|
-
Gruf.logger.error("Failed to send resource exhausted response: #{e.message}")
|
99
|
-
else
|
100
|
-
warn "Failed to send resource exhausted response: #{e.message}"
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
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
|