lean_pool 0.1.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.
data/README.md ADDED
@@ -0,0 +1,324 @@
1
+ # LeanPool
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/lean_pool.svg)](https://badge.fury.io/rb/lean_pool)
4
+ [![Build Status](https://github.com/half-blood-labs/lean_pool/workflows/CI/badge.svg)](https://github.com/half-blood-labs/lean_pool/actions)
5
+ [![Downloads](https://img.shields.io/gem/dt/lean_pool.svg)](https://rubygems.org/gems/lean_pool)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.txt)
7
+ [![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-red.svg)](https://www.ruby-lang.org/)
8
+
9
+ A lightweight, process-free resource pool for Ruby using `concurrent-ruby`. Inspired by Elixir's `nimble_pool`, LeanPool provides efficient resource management without per-resource processes, making it perfect for managing sockets, HTTP connections, ports, and other resources.
10
+
11
+ **Author:** [Junaid Farooq](mailto:fjunaid252@gmail.com) | **Repository:** [half-blood-labs/lean_pool](https://github.com/half-blood-labs/lean_pool)
12
+
13
+ ## Features
14
+
15
+ - 🚀 **Zero Overhead**: Direct resource access without data copying between processes
16
+ - 🔒 **Thread-Safe**: Built on `concurrent-ruby` for reliable thread safety
17
+ - ⚡ **Efficient**: Lazy initialization and smart resource reuse
18
+ - 🎯 **Simple API**: Clean, Ruby-idiomatic interface
19
+ - 🔧 **Flexible**: Works with any resource type (sockets, connections, ports, etc.)
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'lean_pool'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ ```bash
32
+ $ bundle install
33
+ ```
34
+
35
+ Or install it yourself as:
36
+
37
+ ```bash
38
+ $ gem install lean_pool
39
+ ```
40
+
41
+ ## Usage
42
+
43
+ ### Basic Example
44
+
45
+ ```ruby
46
+ require 'lean_pool'
47
+
48
+ # Create a pool of Redis connections
49
+ pool = LeanPool::Pool.new(size: 5) do
50
+ Redis.new(host: "localhost", port: 6379)
51
+ end
52
+
53
+ # Use the pool
54
+ pool.checkout do |redis|
55
+ redis.get("my_key")
56
+ redis.set("my_key", "value")
57
+ end
58
+ ```
59
+
60
+ ### HTTP Connection Pool
61
+
62
+ LeanPool includes a built-in HTTP connection pool for easy HTTP/HTTPS requests:
63
+
64
+ ```ruby
65
+ require 'lean_pool'
66
+
67
+ # Create an HTTP pool
68
+ http_pool = LeanPool::HTTPPool.new("api.example.com", 443, size: 10, use_ssl: true)
69
+
70
+ # Perform GET requests
71
+ response = http_pool.get("/users")
72
+ puts response[:status] # => 200
73
+ puts response[:body]
74
+ puts response[:headers]
75
+
76
+ # Perform POST requests with JSON
77
+ response = http_pool.post(
78
+ "/users",
79
+ body: '{"name":"John","email":"john@example.com"}',
80
+ headers: { "Content-Type" => "application/json" }
81
+ )
82
+
83
+ # With custom timeout
84
+ response = http_pool.get("/slow-endpoint", timeout: 30.0)
85
+
86
+ # Check pool statistics
87
+ stats = http_pool.stats
88
+ # => { size: 10, available: 8, in_use: 2, total: 10 }
89
+
90
+ # Shutdown when done
91
+ http_pool.shutdown
92
+ ```
93
+
94
+ ### Custom HTTP Pool Implementation
95
+
96
+ You can also create your own HTTP pool wrapper:
97
+
98
+ ```ruby
99
+ require 'lean_pool'
100
+ require 'net/http'
101
+
102
+ class CustomHTTPPool
103
+ def self.new_pool(host, port, size: 10)
104
+ LeanPool::Pool.new(
105
+ size: size,
106
+ timeout: 5.0,
107
+ lazy: true,
108
+ pool_state: { host: host, port: port }
109
+ ) do |state|
110
+ Net::HTTP.new(state[:host], state[:port])
111
+ end
112
+ end
113
+
114
+ def self.get(pool, path, opts = {})
115
+ pool.checkout(timeout: opts[:timeout] || 5.0) do |http|
116
+ http.start unless http.started?
117
+ response = http.get(path)
118
+
119
+ if response.code == "200"
120
+ { status: response.code.to_i, body: response.body, headers: response.to_hash }
121
+ else
122
+ { remove: true }
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ # Usage
129
+ pool = CustomHTTPPool.new_pool("api.example.com", 443)
130
+ result = CustomHTTPPool.get(pool, "/users")
131
+ puts result[:body]
132
+ ```
133
+
134
+ ### With Custom State
135
+
136
+ ```ruby
137
+ pool = LeanPool::Pool.new(
138
+ size: 10,
139
+ pool_state: { database: "production", timeout: 30 }
140
+ ) do |state|
141
+ DatabaseConnection.new(
142
+ database: state[:database],
143
+ timeout: state[:timeout]
144
+ )
145
+ end
146
+
147
+ pool.checkout do |db|
148
+ db.query("SELECT * FROM users")
149
+ end
150
+ ```
151
+
152
+ ### Pool Statistics
153
+
154
+ ```ruby
155
+ pool = LeanPool::Pool.new(size: 5) { Redis.new }
156
+
157
+ stats = pool.stats
158
+ # => { size: 5, available: 3, in_use: 2, total: 5 }
159
+ ```
160
+
161
+ ### Shutdown and Reload
162
+
163
+ ```ruby
164
+ # Gracefully shutdown the pool
165
+ pool.shutdown do |resource|
166
+ resource.close if resource.respond_to?(:close)
167
+ end
168
+
169
+ # Reload the pool (useful after forking)
170
+ pool.reload do |resource|
171
+ resource.quit if resource.respond_to?(:quit)
172
+ end
173
+ ```
174
+
175
+ ### Error Handling
176
+
177
+ ```ruby
178
+ begin
179
+ pool.checkout(timeout: 2.0) do |resource|
180
+ resource.perform_operation
181
+ end
182
+ rescue LeanPool::TimeoutError => e
183
+ puts "Pool is busy, try again later"
184
+ rescue LeanPool::ShutdownError => e
185
+ puts "Pool is shutdown"
186
+ rescue LeanPool::ResourceError => e
187
+ puts "Failed to create resource: #{e.message}"
188
+ end
189
+ ```
190
+
191
+ ## Comparison with Other Pooling Solutions
192
+
193
+ | Feature | LeanPool | connection_pool | pond |
194
+ |---------|----------|-----------------|------|
195
+ | Process-free | ✅ | ❌ | ❌ |
196
+ | Direct resource access | ✅ | ❌ | ❌ |
197
+ | Zero data copying | ✅ | ❌ | ❌ |
198
+ | Thread-safe | ✅ | ✅ | ✅ |
199
+ | Lazy initialization | ✅ | ✅ | ✅ |
200
+ | Built on concurrent-ruby | ✅ | ❌ | ❌ |
201
+
202
+ ## When to Use LeanPool
203
+
204
+ **Use LeanPool when:**
205
+ - You need direct access to resources (sockets, ports, HTTP connections)
206
+ - You want to avoid data copying between processes
207
+ - You're managing resources that don't need per-resource processes
208
+ - You need high-performance resource pooling
209
+
210
+ **Don't use LeanPool when:**
211
+ - You're managing processes (use process-based pools instead)
212
+ - You need multiplexing (HTTP/2, etc.)
213
+ - Resources require per-resource process isolation
214
+
215
+ ## Requirements
216
+
217
+ - Ruby >= 3.0.0
218
+ - concurrent-ruby >= 1.3.0
219
+
220
+ ## Development
221
+
222
+ After checking out the repo, run:
223
+
224
+ ```bash
225
+ $ bundle install
226
+ $ bundle exec rspec
227
+ ```
228
+
229
+ ### Running Tests Locally
230
+
231
+ **Quick test run:**
232
+ ```bash
233
+ $ bundle exec rspec
234
+ ```
235
+
236
+ **With documentation format:**
237
+ ```bash
238
+ $ bundle exec rspec --format documentation
239
+ ```
240
+
241
+ **Run specific test files:**
242
+ ```bash
243
+ $ bundle exec rspec spec/lean_pool/pool_spec.rb
244
+ $ bundle exec rspec spec/lean_pool/http_pool_spec.rb
245
+ $ bundle exec rspec spec/lean_pool/errors_spec.rb
246
+ ```
247
+
248
+ **Simulate CI locally:**
249
+ ```bash
250
+ $ ./.github/workflows/test-local.sh
251
+ ```
252
+
253
+ This script will:
254
+ - Check Ruby version
255
+ - Install dependencies
256
+ - Validate syntax of all Ruby files
257
+ - Run RuboCop linting
258
+ - Run the full test suite
259
+ - Display test summary
260
+
261
+ **Using Rake:**
262
+ ```bash
263
+ $ bundle exec rake # Runs tests + linting
264
+ $ bundle exec rake spec # Runs only tests
265
+ ```
266
+
267
+ ## Testing
268
+
269
+ The project includes comprehensive test coverage with over 198 test cases covering:
270
+
271
+ - Pool initialization and configuration
272
+ - Resource checkout and lifecycle management
273
+ - Thread safety and concurrent operations
274
+ - Timeout handling and error recovery
275
+ - HTTP connection pooling
276
+ - Integration scenarios and real-world use cases
277
+ - Edge cases and boundary conditions
278
+
279
+ Run the full test suite:
280
+
281
+ ```bash
282
+ $ bundle exec rspec
283
+ ```
284
+
285
+ ## Project Status
286
+
287
+ - ✅ **CI/CD**: Automated testing with GitHub Actions (testing on Ruby 3.0, 3.1, 3.2, 3.3)
288
+ - ✅ **Test Coverage**: Comprehensive test suite with 198+ test cases
289
+ - ✅ **Documentation**: Complete YARD documentation for all modules
290
+ - ✅ **Thread Safety**: Built on concurrent-ruby for reliable concurrency
291
+ - ✅ **Production Ready**: Suitable for production use
292
+ - ✅ **Code Quality**: RuboCop linting and syntax validation
293
+
294
+ ## CI/CD Status
295
+
296
+ The project uses GitHub Actions for continuous integration:
297
+
298
+ - **Test Matrix**: Tests run on Ruby 3.0, 3.1, 3.2, and 3.3
299
+ - **Linting**: RuboCop code quality checks
300
+ - **Syntax Validation**: Automatic Ruby syntax checking
301
+ - **Status Badge**: [![Build Status](https://github.com/half-blood-labs/lean_pool/workflows/CI/badge.svg)](https://github.com/half-blood-labs/lean_pool/actions)
302
+
303
+ View the latest CI runs: [GitHub Actions](https://github.com/half-blood-labs/lean_pool/actions)
304
+
305
+ **Note**: After publishing the gem to RubyGems, the badges will automatically update to show download counts and build status.
306
+
307
+ ## Contributing
308
+
309
+ Bug reports and pull requests are welcome on GitHub at https://github.com/half-blood-labs/lean_pool.
310
+
311
+ ## Author
312
+
313
+ **Junaid Farooq**
314
+
315
+ - Email: fjunaid252@gmail.com
316
+ - GitHub: [@half-blood-labs](https://github.com/half-blood-labs)
317
+
318
+ ## License
319
+
320
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
321
+
322
+ ## Inspiration
323
+
324
+ This gem is inspired by [nimble_pool](https://github.com/dashbitco/nimble_pool) from the Elixir ecosystem, adapted for Ruby's concurrency model using `concurrent-ruby`.
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: %i[spec rubocop]
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/lean_pool"
5
+
6
+ # Example 1: Basic resource pool
7
+ puts "=== Example 1: Basic Resource Pool ==="
8
+ pool = LeanPool::Pool.new(size: 3) do
9
+ { id: rand(1000), created_at: Time.now }
10
+ end
11
+
12
+ 3.times do |i|
13
+ result = pool.checkout do |resource|
14
+ puts "Using resource #{i + 1}: #{resource[:id]}"
15
+ resource[:id] * 2
16
+ end
17
+ puts "Result: #{result}"
18
+ end
19
+
20
+ puts "\nPool stats: #{pool.stats}"
21
+
22
+ # Example 2: HTTP Pool
23
+ puts "\n=== Example 2: HTTP Pool ==="
24
+ begin
25
+ http_pool = LeanPool::HTTPPool.new("httpbin.org", 80, size: 5)
26
+
27
+ response = http_pool.get("/get")
28
+ puts "Status: #{response[:status]}"
29
+ puts "Response size: #{response[:body]&.length || 0} bytes"
30
+
31
+ puts "\nHTTP Pool stats: #{http_pool.stats}"
32
+
33
+ http_pool.shutdown
34
+ rescue => e
35
+ puts "HTTP example failed (network may be unavailable): #{e.message}"
36
+ end
37
+
38
+ # Example 3: Custom resource with state
39
+ puts "\n=== Example 3: Custom Resource with State ==="
40
+ class SimpleCounter
41
+ def initialize(initial_value = 0)
42
+ @value = initial_value
43
+ end
44
+
45
+ def increment
46
+ @value += 1
47
+ end
48
+
49
+ def value
50
+ @value
51
+ end
52
+ end
53
+
54
+ counter_pool = LeanPool::Pool.new(
55
+ size: 2,
56
+ pool_state: { start: 10 }
57
+ ) do |state|
58
+ SimpleCounter.new(state[:start])
59
+ end
60
+
61
+ results = []
62
+ 2.times do
63
+ result = counter_pool.checkout do |counter|
64
+ counter.increment
65
+ counter.value
66
+ end
67
+ results << result
68
+ end
69
+
70
+ puts "Counter results: #{results}"
71
+ puts "Counter pool stats: #{counter_pool.stats}"
72
+
73
+ puts "\n=== All examples completed! ==="
data/lean_pool.gemspec ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/lean_pool/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "lean_pool"
7
+ spec.version = LeanPool::VERSION
8
+ spec.authors = ["Junaid Farooq"]
9
+ spec.email = ["fjunaid252@gmail.com"]
10
+
11
+ spec.summary = "A lightweight, process-free resource pool for Ruby using concurrent-ruby"
12
+ spec.description = <<~DESC
13
+ LeanPool is a tiny, efficient resource pool implementation for Ruby that provides
14
+ direct resource access without per-resource processes. Inspired by Elixir's nimble_pool,
15
+ it's perfect for managing sockets, HTTP connections, ports, and other resources that
16
+ need efficient pooling with minimal overhead.
17
+ DESC
18
+ spec.homepage = "https://github.com/half-blood-labs/lean_pool"
19
+ spec.license = "MIT"
20
+ spec.required_ruby_version = ">= 3.0.0"
21
+
22
+ spec.metadata["homepage_uri"] = spec.homepage
23
+ spec.metadata["source_code_uri"] = "https://github.com/half-blood-labs/lean_pool"
24
+ spec.metadata["changelog_uri"] = "https://github.com/half-blood-labs/lean_pool/blob/main/CHANGELOG.md"
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ spec.files = Dir.chdir(__dir__) do
28
+ `git ls-files -z`.split("\x0").reject do |f|
29
+ (File.expand_path(f) == __FILE__) ||
30
+ f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
31
+ end
32
+ end
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_dependency "concurrent-ruby", "~> 1.3"
38
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeanPool
4
+ # Base error class for all LeanPool errors.
5
+ #
6
+ # All LeanPool-specific errors inherit from this class, allowing you to
7
+ # catch all LeanPool errors with a single rescue clause.
8
+ #
9
+ # @example Catching all LeanPool errors
10
+ # begin
11
+ # pool.checkout { |r| r.operation }
12
+ # rescue LeanPool::Error => e
13
+ # puts "LeanPool error: #{e.message}"
14
+ # end
15
+ #
16
+ # @since 0.1.0
17
+ class Error < StandardError; end
18
+
19
+ # Raised when a pool checkout operation times out.
20
+ #
21
+ # This error is raised when attempting to checkout a resource from a pool
22
+ # that is at capacity and no resources become available within the specified
23
+ # timeout period.
24
+ #
25
+ # @example Handling timeout errors
26
+ # begin
27
+ # pool.checkout(timeout: 1.0) { |r| r.operation }
28
+ # rescue LeanPool::TimeoutError => e
29
+ # puts "Pool is busy, try again later"
30
+ # end
31
+ #
32
+ # @since 0.1.0
33
+ class TimeoutError < Error; end
34
+
35
+ # Raised when attempting to use a pool that has been shutdown.
36
+ #
37
+ # This error is raised when attempting to checkout a resource from a pool
38
+ # that has been shut down. Once a pool is shutdown, it cannot be used for
39
+ # new operations unless it is reloaded.
40
+ #
41
+ # @example Handling shutdown errors
42
+ # begin
43
+ # pool.checkout { |r| r.operation }
44
+ # rescue LeanPool::ShutdownError => e
45
+ # puts "Pool has been shutdown"
46
+ # end
47
+ #
48
+ # @see Pool#shutdown
49
+ # @see Pool#reload
50
+ # @since 0.1.0
51
+ class ShutdownError < Error; end
52
+
53
+ # Raised when resource initialization fails.
54
+ #
55
+ # This error is raised when the resource initializer block raises an exception
56
+ # during resource creation. The original error is preserved and can be accessed
57
+ # via the `cause` attribute.
58
+ #
59
+ # @example Handling resource errors
60
+ # begin
61
+ # pool.checkout { |r| r.operation }
62
+ # rescue LeanPool::ResourceError => e
63
+ # puts "Failed to create resource: #{e.message}"
64
+ # puts "Original error: #{e.cause}"
65
+ # end
66
+ #
67
+ # @since 0.1.0
68
+ class ResourceError < Error; end
69
+ end