resource_pool 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "rspec", "~> 2.3.0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Aaron Qian
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,60 @@
1
+ = resource_pool
2
+
3
+ This is a generic connection pool / resource pool implementation. The initial code is largely taken from `ThreadedConnectionPool` class from `Sequel` gem and adapted for more general use.
4
+
5
+ == Install
6
+
7
+ gem install resource_pool
8
+
9
+ == Usage
10
+
11
+ require 'resource_pool'
12
+
13
+ memcache_pool = ResourcePool.new({
14
+ :max_size => 10, # Max amount of resource allowed to create. Default is 4
15
+ :pool_timeout => 4, # Seconds to wait when aquiring a free resource. Default is 2
16
+ :pool_sleep_time => 0.1, # Seconds to wait for retry aquiring resource. Default is 0.001
17
+ :delete_proc => lambda{ |res| } # a proc used to close / delete the resource. Optional
18
+ }) do
19
+ # create your resource here.
20
+ # A resource can be anything such as TCP, persistent HTTP, database, nosql
21
+ # Note: must return the instantiated resource
22
+ Memcache.new MemCache.new 'host:11211'
23
+ end
24
+
25
+ # using defaults for redis pool
26
+ redis_pool = ResourcePool.new do
27
+ Redis.new(:host => "10.0.1.1", :port => 6380)
28
+ end
29
+
30
+ # Use the resource:
31
+ threads = []
32
+ 50.times do
33
+ threads <<Thread.new do
34
+ # there are 4 redis connections
35
+ # can now be shared safely with 50 threads
36
+ redis_pool.hold do |redis|
37
+ redis.set "foo", "bar"
38
+ redis.get "foo"
39
+ end
40
+ end
41
+ end
42
+
43
+ threads.each(&:join)
44
+
45
+
46
+ == Contributing to resource_pool
47
+
48
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
49
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
50
+ * Fork the project
51
+ * Start a feature/bugfix branch
52
+ * Commit and push until you are happy with your contribution
53
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
54
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
55
+
56
+ == Copyright
57
+
58
+ Copyright (c) 2011 Aaron Qian. See LICENSE.txt for
59
+ further details.
60
+
data/Rakefile ADDED
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "resource_pool"
18
+ gem.homepage = "http://github.com/aq1018/resource_pool"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A gem for generic connection pooling / resource pooling. }
21
+ gem.description = %Q{A gem for generic connection pooling / resource pooling. The code is mostly taken from Sequel gem's ThreadedConnectionPool class, and adapted for more general use.}
22
+ gem.email = "aq1018@gmail.com"
23
+ gem.authors = ["Aaron Qian"]
24
+ end
25
+ Jeweler::RubygemsDotOrgTasks.new
26
+
27
+ require 'rspec/core'
28
+ require 'rspec/core/rake_task'
29
+ RSpec::Core::RakeTask.new(:spec) do |spec|
30
+ spec.pattern = FileList['spec/**/*_spec.rb']
31
+ end
32
+
33
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
34
+ spec.pattern = 'spec/**/*_spec.rb'
35
+ spec.rcov = true
36
+ spec.rcov_opts = "--exclude ~\/.rvm,spec"
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "resource_pool #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,100 @@
1
+ class ResourcePool
2
+ class ResourcePoolError < RuntimeError; end
3
+ class PoolTimeout < ResourcePoolError; end
4
+ class BadResource < ResourcePoolError; end
5
+ class InvalidCreateProc < ResourcePoolError; end
6
+
7
+ attr_reader :max_size, :pool, :allocated
8
+
9
+ def initialize(opts={}, &block)
10
+ @max_size = opts[:max_size] || 4
11
+ @create_proc = block
12
+ @pool = []
13
+ @allocated = {}
14
+ @mutex = Mutex.new
15
+ @timeout = opts[:pool_timeout] || 2
16
+ @sleep_time = opts[:pool_sleep_time] || 0.001
17
+ @delete_proc = opts[:delete_proc]
18
+ end
19
+
20
+ def size
21
+ @allocated.length + @pool.length
22
+ end
23
+
24
+ def release_all(&block)
25
+ block ||= @delete_proc
26
+ sync do
27
+ @pool.each{|res| block.call(res)} if block
28
+ @pool.clear
29
+ end
30
+ end
31
+
32
+ def hold
33
+ t = Thread.current
34
+ if res = owned_resource(t)
35
+ return yield(res)
36
+
37
+ end
38
+ begin
39
+ unless res = acquire(t)
40
+ time = Time.now
41
+ timeout = time + @timeout
42
+ sleep_time = @sleep_time
43
+ sleep sleep_time
44
+ until res = acquire(t)
45
+ raise PoolTimeout if Time.now > timeout
46
+ sleep sleep_time
47
+ end
48
+ end
49
+ yield res
50
+ rescue BadResource
51
+ old_res = res
52
+ res = nil
53
+ @delete_proc.call(res) if @delete_proc && old_res
54
+ @allocated.delete(t)
55
+ raise
56
+ ensure
57
+ sync{release(t)} if res
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def owned_resource(thread)
64
+ sync{ @allocated[thread] }
65
+ end
66
+
67
+ def acquire(thread)
68
+ sync do
69
+ res = available
70
+ @allocated[thread] = res if res
71
+ end
72
+ end
73
+
74
+ def release(thread)
75
+ @pool << @allocated.delete(thread)
76
+ end
77
+
78
+ def available
79
+ @pool.pop || make_new
80
+ end
81
+
82
+ def make_new
83
+ salvage if size >= @max_size
84
+ size < @max_size ? create_resource : nil
85
+ end
86
+
87
+ def salvage
88
+ @allocated.keys.each{ |t| release(t) unless t.alive? }
89
+ end
90
+
91
+ def create_resource
92
+ resource = @create_proc.call
93
+ raise InvalidCreateProc, "create_proc returned nil" unless resource
94
+ resource
95
+ end
96
+
97
+ def sync
98
+ @mutex.synchronize{yield}
99
+ end
100
+ end
@@ -0,0 +1,319 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "ResourcePool" do
4
+
5
+ describe "Integration" do
6
+ it "should work in small multithreaded environment" do
7
+ pool = ResourcePool.new{ Hash.new }
8
+ threads = []
9
+ 100.times do
10
+ threads << Thread.new do
11
+ pool.hold do |res|
12
+ Thread.pass
13
+ res.class.should == Hash
14
+ end
15
+ end
16
+ end
17
+
18
+ threads.each(&:join)
19
+ pool.pool.size.should be_between(1, 4)
20
+ pool.allocated.size.should == 0
21
+ end
22
+
23
+ it "should work in large multithreaded environment" do
24
+ pool = ResourcePool.new(:max_size => 100){ Hash.new }
25
+ threads = []
26
+ 1000.times do
27
+ threads << Thread.new do
28
+ pool.hold do |res|
29
+ Thread.pass
30
+ res.class.should == Hash
31
+ end
32
+ end
33
+ end
34
+
35
+ threads.each(&:join)
36
+ pool.pool.size.should be_between(1, 1000)
37
+ pool.allocated.size.should == 0
38
+ end
39
+ end
40
+
41
+ describe "#size" do
42
+ it "should return the size of pool plus allocated" do
43
+ pool = ResourcePool.new{ Hash.new }
44
+ pool.allocated.should_receive(:length).and_return(1)
45
+ pool.pool.should_receive(:length).and_return(1)
46
+ pool.size.should == 2
47
+ end
48
+ end
49
+
50
+ describe "#create_resource" do
51
+ it "should create new resource" do
52
+ pool = ResourcePool.new{ Hash.new }
53
+ res = pool.send :create_resource
54
+ res.class.should == Hash
55
+ end
56
+
57
+ it "should raise error if create_proc returns nil" do
58
+ pool = ResourcePool.new{ nil }
59
+ lambda {
60
+ pool.send :create_resource
61
+ }.should raise_error(ResourcePool::InvalidCreateProc)
62
+ end
63
+ end
64
+
65
+ describe "#salvage" do
66
+ it "should salvage resources from dead thread" do
67
+ pool = ResourcePool.new{ Hash.new }
68
+ # create a dead thread
69
+ thread = Thread.new{ nil }
70
+ thread.join
71
+ pool.allocated[thread] = Hash.new
72
+ pool.send :salvage
73
+
74
+ pool.allocated[thread].should be_nil
75
+ pool.pool.length.should == 1
76
+ end
77
+ end
78
+
79
+ describe "#make_new" do
80
+ it "should invoke salvage if reach max size" do
81
+ pool = ResourcePool.new{ Hash.new }
82
+ pool.should_receive(:size).twice.and_return(4, 3)
83
+ pool.should_receive(:salvage)
84
+ pool.should_receive(:create_resource).and_return(Hash.new)
85
+
86
+ pool.send(:make_new).class.should == Hash
87
+ end
88
+
89
+ it "should not invoke salvage if max size is not reached" do
90
+ pool = ResourcePool.new{ Hash.new }
91
+ pool.should_receive(:size).twice.and_return(3, 3)
92
+ pool.should_not_receive(:salvage)
93
+ pool.should_receive(:create_resource).and_return(Hash.new)
94
+
95
+ pool.send(:make_new).class.should == Hash
96
+ end
97
+
98
+ it "should not invoke create_resouce if max size is reached" do
99
+ pool = ResourcePool.new{ Hash.new }
100
+ pool.should_receive(:size).twice.and_return(4, 4)
101
+ pool.should_receive(:salvage)
102
+ pool.should_not_receive(:create_resource)
103
+
104
+ pool.send(:make_new).should == nil
105
+ end
106
+ end
107
+
108
+ describe "#availabe" do
109
+ it "should pop from pool" do
110
+ pool = ResourcePool.new{ Hash.new }
111
+ pool.pool.should_receive(:pop)
112
+ pool.send(:available)
113
+ end
114
+
115
+ it "should use resource from pool if available" do
116
+ res = {}
117
+ pool = ResourcePool.new{ Hash.new }
118
+ pool.pool.should_receive(:pop).and_return(res)
119
+ pool.should_not_receive(:make_new)
120
+ pool.send(:available).should == res
121
+ end
122
+
123
+ it "should invoke #make_new if pool is empty" do
124
+ res = {}
125
+ pool = ResourcePool.new{ Hash.new }
126
+ pool.pool.should_receive(:pop).and_return(nil)
127
+ pool.should_receive(:make_new).and_return(res)
128
+ pool.send(:available).should == res
129
+ end
130
+ end
131
+
132
+ describe "#release" do
133
+ it "should remove resource from allocated and return to pool" do
134
+ mock_thread = {}
135
+ res = {}
136
+ pool = ResourcePool.new{ Hash.new }
137
+ pool.allocated.should_receive(:delete).
138
+ with(mock_thread).
139
+ and_return(res)
140
+ pool.send :release, mock_thread
141
+ pool.pool.first.should == res
142
+ end
143
+ end
144
+
145
+ describe "#acquire" do
146
+ it "should call #available" do
147
+ mock_thread = {}
148
+ res = {}
149
+ pool = ResourcePool.new{ Hash.new }
150
+ pool.should_receive(:available).once
151
+
152
+ pool.send :acquire, mock_thread
153
+ end
154
+
155
+ it "should mark resource in use if resource is available" do
156
+ mock_thread = {}
157
+ res = {}
158
+ pool = ResourcePool.new{ Hash.new }
159
+ pool.should_receive(:available).and_return(res)
160
+ pool.allocated.
161
+ should_receive(:[]=).
162
+ with(mock_thread, res).
163
+ and_return(res)
164
+
165
+ pool.send(:acquire, mock_thread).should == res
166
+ end
167
+ end
168
+
169
+ describe "#owned_resource" do
170
+ it "should return resouce of specified thread" do
171
+ mock_thread = {}
172
+ res = {}
173
+ pool = ResourcePool.new{ Hash.new }
174
+ pool.allocated.
175
+ should_receive(:[]).
176
+ with(mock_thread).
177
+ and_return(res)
178
+ pool.send(:owned_resource, mock_thread).should == res
179
+ end
180
+
181
+ it "should return nil if no resource was aquired" do
182
+ mock_thread = {}
183
+ pool = ResourcePool.new{ Hash.new }
184
+ pool.allocated.
185
+ should_receive(:[]).
186
+ with(mock_thread).
187
+ and_return(nil)
188
+ pool.send(:owned_resource, mock_thread).should == nil
189
+ end
190
+ end
191
+
192
+ describe "#release_all" do
193
+ before :each do
194
+ @pool = ResourcePool.new{ Hash.new }
195
+ 4.times{ @pool.pool << Hash.new }
196
+ end
197
+
198
+ it "should remove all resource from pool" do
199
+ @pool.release_all
200
+ @pool.size.should == 0
201
+ end
202
+
203
+ it "should call block if available" do
204
+ counter = 0
205
+ @pool.release_all{|res| counter += 1 }
206
+ counter.should == 4
207
+ end
208
+
209
+ it "should call @delete_proc if no block given" do
210
+ counter = 0
211
+ delete_proc = lambda {|res| counter += 1 }
212
+ @pool.instance_variable_set(:@delete_proc, delete_proc)
213
+ @pool.release_all
214
+ counter.should == 4
215
+ end
216
+
217
+ it "should call block if both block and @delete_proc are avaiable" do
218
+ counter1 = 0
219
+ counter2 = 0
220
+ delete_proc = lambda {|res| counter1 += 1 }
221
+ @pool.instance_variable_set(:@delete_proc, delete_proc)
222
+ @pool.release_all{|res| counter2 += 1}
223
+ counter1.should == 0
224
+ counter2.should == 4
225
+ end
226
+ end
227
+
228
+ describe "#hold" do
229
+ before :each do
230
+ @res = {}
231
+ @pool = ResourcePool.new{ Hash.new }
232
+ end
233
+
234
+ it "should use already acquired resource" do
235
+ @pool.should_receive(:owned_resource).
236
+ with(Thread.current).
237
+ and_return(@res)
238
+ @pool.should_not_receive(:acquire)
239
+ @pool.should_not_receive(:release)
240
+
241
+ @pool.hold do |res|
242
+ res.should == @res
243
+ end
244
+ end
245
+
246
+ it "should aquire and release resource" do
247
+ @pool.should_receive(:acquire).
248
+ with(Thread.current).
249
+ and_return(@res)
250
+ @pool.should_receive(:release).
251
+ with(Thread.current)
252
+
253
+ @pool.hold do |res|
254
+ res.should == @res
255
+ end
256
+ end
257
+
258
+ it "should retry aquire" do
259
+ args = []
260
+ 1000.times{ args << nil }
261
+ args << @res
262
+ @pool.should_receive(:acquire).
263
+ and_return(*args)
264
+
265
+ start = Time.now
266
+ @pool.hold do |res|
267
+ res.should == @res
268
+ end
269
+ delta = Time.now - start
270
+ delta.should be_between(0.8, 1.2)
271
+ end
272
+
273
+ it "should timeout after 2 seconds" do
274
+ @pool.should_receive(:acquire).
275
+ any_number_of_times.
276
+ and_return(nil)
277
+ @pool.should_not_receive(:release)
278
+
279
+ start = Time.now
280
+ lambda {
281
+ @pool.hold{ |res| res.should == @res }
282
+ }.should raise_error(ResourcePool::PoolTimeout)
283
+
284
+ delta = Time.now - start
285
+ delta.should be_between(1.99, 2.01)
286
+ end
287
+
288
+ it "should delete bad resource" do
289
+ @pool.should_receive(:acquire).
290
+ and_return(@res)
291
+ @pool.should_not_receive(:release)
292
+ @pool.allocated.should_receive(:delete).
293
+ with(Thread.current)
294
+
295
+ lambda {
296
+ @pool.hold{ |res| raise ResourcePool::BadResource }
297
+ }.should raise_error(ResourcePool::BadResource)
298
+ end
299
+
300
+ it "should call delete_proc if avaiable" do
301
+ delete_proc_called = false
302
+ @pool.should_receive(:acquire).
303
+ and_return(@res)
304
+ @pool.should_not_receive(:release)
305
+ @pool.allocated.should_receive(:delete).
306
+ with(Thread.current)
307
+
308
+ @pool.instance_variable_set(:@delete_proc, lambda{|res|
309
+ delete_proc_called = true
310
+ })
311
+
312
+ lambda {
313
+ @pool.hold{ |res| raise ResourcePool::BadResource }
314
+ }.should raise_error(ResourcePool::BadResource)
315
+
316
+ delete_proc_called.should be_true
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'resource_pool'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resource_pool
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 1
10
+ version: 0.1.1
11
+ platform: ruby
12
+ authors:
13
+ - Aaron Qian
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-10-12 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ hash: 3
27
+ segments:
28
+ - 2
29
+ - 3
30
+ - 0
31
+ version: 2.3.0
32
+ version_requirements: *id001
33
+ name: rspec
34
+ prerelease: false
35
+ type: :development
36
+ - !ruby/object:Gem::Dependency
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ~>
41
+ - !ruby/object:Gem::Version
42
+ hash: 23
43
+ segments:
44
+ - 1
45
+ - 0
46
+ - 0
47
+ version: 1.0.0
48
+ version_requirements: *id002
49
+ name: bundler
50
+ prerelease: false
51
+ type: :development
52
+ - !ruby/object:Gem::Dependency
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ hash: 7
59
+ segments:
60
+ - 1
61
+ - 6
62
+ - 4
63
+ version: 1.6.4
64
+ version_requirements: *id003
65
+ name: jeweler
66
+ prerelease: false
67
+ type: :development
68
+ - !ruby/object:Gem::Dependency
69
+ requirement: &id004 !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ hash: 3
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ version_requirements: *id004
79
+ name: rcov
80
+ prerelease: false
81
+ type: :development
82
+ description: A gem for generic connection pooling / resource pooling. The code is mostly taken from Sequel gem's ThreadedConnectionPool class, and adapted for more general use.
83
+ email: aq1018@gmail.com
84
+ executables: []
85
+
86
+ extensions: []
87
+
88
+ extra_rdoc_files:
89
+ - LICENSE.txt
90
+ - README.rdoc
91
+ files:
92
+ - .document
93
+ - .rspec
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.rdoc
97
+ - Rakefile
98
+ - VERSION
99
+ - lib/resource_pool.rb
100
+ - spec/resource_pool_spec.rb
101
+ - spec/spec_helper.rb
102
+ homepage: http://github.com/aq1018/resource_pool
103
+ licenses:
104
+ - MIT
105
+ post_install_message:
106
+ rdoc_options: []
107
+
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 3
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ hash: 3
125
+ segments:
126
+ - 0
127
+ version: "0"
128
+ requirements: []
129
+
130
+ rubyforge_project:
131
+ rubygems_version: 1.8.6
132
+ signing_key:
133
+ specification_version: 3
134
+ summary: A gem for generic connection pooling / resource pooling.
135
+ test_files: []
136
+