pond 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +55 -0
- data/Rakefile +7 -0
- data/lib/pond.rb +136 -0
- data/lib/pond/version.rb +3 -0
- data/pond.gemspec +24 -0
- data/spec/checkout_spec.rb +293 -0
- data/spec/config_spec.rb +117 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/wrapper_spec.rb +62 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 60082a3f35a388cf665c609388ee4025e58006d1
|
4
|
+
data.tar.gz: 1e0ed085f1a3f3943c85ce1f00976f2d892c1e33
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7e1b8250177dbd7b8ee56d1750debade92eabb7ad22b9b778d72f8465083d3c0071f1674713a0d44a06aa3d6df18ac549e7bfade50c3a24293a68168dbf0dde7
|
7
|
+
data.tar.gz: 24b0bd2e914f6932bdb4ea2e4c532543a3a7928e8c01d11326ed44b5254c261b1ee3032408c0f42092a5a8a6bd48e6d8406b6f1ddaee3dce5ddf4fd5202b2529
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Chris Hanks
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# Pond
|
2
|
+
|
3
|
+
Pond is a gem that offers thread-safe object pooling. It can wrap anything that is costly to instantiate, but is usually used for connections. It is intentionally very similar to the `connection_pool` gem, but is intended to be more efficient and flexible. It instantiates objects lazily by default, which is important for things with high overhead like Postgres connections. It can also be dynamically resized.
|
4
|
+
|
5
|
+
Also, it was pretty fun to write.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'pond'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install pond
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
require 'pond'
|
24
|
+
require 'redis'
|
25
|
+
|
26
|
+
$redis_pond = Pond.new(:maximum_size => 5, :timeout => 0.5) { Redis.new }
|
27
|
+
|
28
|
+
# No connections are established until we need one:
|
29
|
+
$redis_pond.checkout do |redis|
|
30
|
+
redis.incr 'my_counter'
|
31
|
+
redis.lpush 'my_list', 'item'
|
32
|
+
end
|
33
|
+
|
34
|
+
# Alternatively, wrap it:
|
35
|
+
$redis = Pond.wrap(:maximum_size => 5, :timeout => 0.5) { Redis.new }
|
36
|
+
|
37
|
+
# You can now use $redis as you normally would.
|
38
|
+
$redis.incr 'my_counter'
|
39
|
+
$redis.lpush 'my_list', 'item'
|
40
|
+
|
41
|
+
$redis.pipelined do
|
42
|
+
# All these commands go to the same Redis connection, and so are pipelined correctly.
|
43
|
+
$redis.incr 'my_counter'
|
44
|
+
$redis.lpush 'my_list', 'item'
|
45
|
+
end
|
46
|
+
|
47
|
+
Options:
|
48
|
+
* :maximum_size - The maximum number of objects you want the pool to contain. The default is 10.
|
49
|
+
* :timeout - When attempting to check out an object but none are available, how many seconds to wait before raising a `Pond::Timeout` error. The default is 1.
|
50
|
+
* :collection - How to manage the objects in the pool. The default is :queue, meaning that pond.checkout will yield the object that hasn't been used in the longest period of time. This is to prevent connections from becoming 'stale'. The alternative is :stack, so checkout will yield the object that has most recently been returned to the pool. This would be preferable if you're using connections that have their own logic for becoming idle in periods of low activity.
|
51
|
+
* :eager - Set this to true to fill the pool with instantiated objects when it is created, similar to how `connection_pool` works.
|
52
|
+
|
53
|
+
## Contributing
|
54
|
+
|
55
|
+
I don't plan on adding too many more features to Pond, since I want to keep its design simple. If there's something you'd like to see it do, open an issue so we can discuss it before going to the trouble of creating a pull request.
|
data/Rakefile
ADDED
data/lib/pond.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
require 'pond/version'
|
4
|
+
|
5
|
+
class Pond
|
6
|
+
class Timeout < StandardError; end
|
7
|
+
|
8
|
+
attr_reader :allocated, :available, :timeout, :collection, :maximum_size
|
9
|
+
|
10
|
+
def initialize(options = {}, &block)
|
11
|
+
@block = block
|
12
|
+
@monitor = Monitor.new
|
13
|
+
@cv = Monitor::ConditionVariable.new(@monitor)
|
14
|
+
|
15
|
+
maximum_size = options.fetch :maximum_size, 10
|
16
|
+
|
17
|
+
@allocated = {}
|
18
|
+
@available = Array.new(options[:eager] ? maximum_size : 0, &block)
|
19
|
+
|
20
|
+
self.timeout = options.fetch :timeout, 1
|
21
|
+
self.collection = options.fetch :collection, :queue
|
22
|
+
self.maximum_size = maximum_size
|
23
|
+
end
|
24
|
+
|
25
|
+
def checkout(&block)
|
26
|
+
if object = current_object
|
27
|
+
yield object
|
28
|
+
else
|
29
|
+
checkout_object(&block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def size
|
34
|
+
sync { @available.size + @allocated.size }
|
35
|
+
end
|
36
|
+
|
37
|
+
def timeout=(timeout)
|
38
|
+
raise "Bad value for Pond timeout: #{timeout.inspect}" unless Numeric === timeout && timeout >= 0
|
39
|
+
sync { @timeout = timeout }
|
40
|
+
end
|
41
|
+
|
42
|
+
def collection=(type)
|
43
|
+
raise "Bad value for Pond collection: #{type.inspect}" unless [:stack, :queue].include?(type)
|
44
|
+
sync { @collection = type }
|
45
|
+
end
|
46
|
+
|
47
|
+
def maximum_size=(max)
|
48
|
+
raise "Bad value for Pond maximum_size: #{max.inspect}" unless Integer === max && max >= 0
|
49
|
+
sync do
|
50
|
+
@maximum_size = max
|
51
|
+
{} until size <= max || pop_object.nil?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def checkout_object
|
58
|
+
lock_object
|
59
|
+
yield current_object
|
60
|
+
ensure
|
61
|
+
unlock_object
|
62
|
+
end
|
63
|
+
|
64
|
+
def lock_object
|
65
|
+
deadline = Time.now + @timeout
|
66
|
+
|
67
|
+
until current_object
|
68
|
+
raise Timeout if (time_left = deadline - Time.now) < 0
|
69
|
+
|
70
|
+
sync do
|
71
|
+
if object = get_object(time_left)
|
72
|
+
set_current_object(object)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# We need to protect changes to @allocated and @available with the monitor
|
78
|
+
# so that #size always returns the correct value. But, we don't want to
|
79
|
+
# call the instantiation block while we have the lock, since it may take a
|
80
|
+
# long time to return. So, we set the checked-out object to the block as a
|
81
|
+
# signal that it needs to be called.
|
82
|
+
set_current_object(@block.call) if current_object == @block
|
83
|
+
end
|
84
|
+
|
85
|
+
def unlock_object
|
86
|
+
sync do
|
87
|
+
object = @allocated.delete(Thread.current)
|
88
|
+
|
89
|
+
if object && object != @block && size < maximum_size
|
90
|
+
@available << object
|
91
|
+
@cv.signal
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_object(timeout)
|
97
|
+
pop_object || size < maximum_size && @block || @cv.wait(timeout) && false
|
98
|
+
end
|
99
|
+
|
100
|
+
def pop_object
|
101
|
+
case collection
|
102
|
+
when :queue then @available.shift
|
103
|
+
when :stack then @available.pop
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def current_object
|
108
|
+
sync { @allocated[Thread.current] }
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_current_object(object)
|
112
|
+
sync { @allocated[Thread.current] = object }
|
113
|
+
end
|
114
|
+
|
115
|
+
def sync(&block)
|
116
|
+
@monitor.synchronize(&block)
|
117
|
+
end
|
118
|
+
|
119
|
+
class << self
|
120
|
+
def wrap(*args, &block)
|
121
|
+
Wrapper.new(*args, &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
class Wrapper < BasicObject
|
126
|
+
attr_reader :pond
|
127
|
+
|
128
|
+
def initialize(*args, &block)
|
129
|
+
@pond = ::Pond.new(*args, &block)
|
130
|
+
end
|
131
|
+
|
132
|
+
def method_missing(*args, &block)
|
133
|
+
@pond.checkout { |object| object.send(*args, &block) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/lib/pond/version.rb
ADDED
data/pond.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pond/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'pond'
|
8
|
+
spec.version = Pond::VERSION
|
9
|
+
spec.authors = ["Chris Hanks"]
|
10
|
+
spec.email = ["christopher.m.hanks@gmail.com"]
|
11
|
+
spec.description = %q{A simple, generic, thread-safe pool for connections or whatever else}
|
12
|
+
spec.summary = %q{A simple, generic, thread-safe pool}
|
13
|
+
spec.homepage = 'https://github.com/chanks/pond'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
end
|
@@ -0,0 +1,293 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pond, "#checkout" do
|
4
|
+
it "should yield objects specified in the block" do
|
5
|
+
pond = Pond.new { 1 }
|
6
|
+
pond.checkout { |i| i.should == 1 }
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return the value returned by the block" do
|
10
|
+
pond = Pond.new { 1 }
|
11
|
+
value = pond.checkout { |i| 'value' }
|
12
|
+
value.should == 'value'
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should instantiate objects when needed" do
|
16
|
+
int = 0
|
17
|
+
pond = Pond.new { int += 1 }
|
18
|
+
|
19
|
+
pond.size.should == 0
|
20
|
+
|
21
|
+
pond.checkout do |i|
|
22
|
+
pond.available.should == []
|
23
|
+
pond.allocated.should == {Thread.current => 1}
|
24
|
+
i.should == 1
|
25
|
+
end
|
26
|
+
|
27
|
+
pond.available.should == [1]
|
28
|
+
pond.allocated.should == {}
|
29
|
+
pond.size.should == 1
|
30
|
+
|
31
|
+
pond.checkout do |i|
|
32
|
+
pond.available.should == []
|
33
|
+
pond.allocated.should == {Thread.current => 1}
|
34
|
+
i.should == 1
|
35
|
+
end
|
36
|
+
|
37
|
+
pond.available.should == [1]
|
38
|
+
pond.allocated.should == {}
|
39
|
+
pond.size.should == 1
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should not instantiate objects in excess of the specified maximum_size" do
|
43
|
+
object = nil
|
44
|
+
pond = Pond.new(:maximum_size => 1) { object = Object.new }
|
45
|
+
object_ids = []
|
46
|
+
|
47
|
+
threads = 20.times.map do
|
48
|
+
pond.checkout do |obj|
|
49
|
+
object_ids << obj.object_id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
object_ids.uniq.should == [object.object_id]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should give different objects to different threads" do
|
57
|
+
int = 0
|
58
|
+
pond = Pond.new { int += 1 }
|
59
|
+
|
60
|
+
q1, q2 = Queue.new, Queue.new
|
61
|
+
|
62
|
+
t = Thread.new do
|
63
|
+
pond.checkout do |i|
|
64
|
+
i.should == 1
|
65
|
+
q1.push nil
|
66
|
+
q2.pop
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
q1.pop
|
71
|
+
|
72
|
+
pond.size.should == 1
|
73
|
+
pond.allocated.should == {t => 1}
|
74
|
+
pond.available.should == []
|
75
|
+
|
76
|
+
pond.checkout { |i| i.should == 2 }
|
77
|
+
|
78
|
+
pond.size.should == 2
|
79
|
+
pond.allocated.should == {t => 1}
|
80
|
+
pond.available.should == [2]
|
81
|
+
|
82
|
+
q2.push nil
|
83
|
+
t.join
|
84
|
+
|
85
|
+
pond.allocated.should == {}
|
86
|
+
pond.available.should == [2, 1]
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should be re-entrant" do
|
90
|
+
pond = Pond.new { Object.new }
|
91
|
+
pond.checkout do |obj1|
|
92
|
+
pond.checkout do |obj2|
|
93
|
+
obj1.should == obj2
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should support a thread checking out objects from distinct Pond instances" do
|
99
|
+
pond1 = Pond.new { [] }
|
100
|
+
pond2 = Pond.new { {} }
|
101
|
+
|
102
|
+
pond1.checkout do |one|
|
103
|
+
pond2.checkout do |two|
|
104
|
+
one.should == []
|
105
|
+
two.should == {}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should yield an object to only one thread when many are waiting" do
|
111
|
+
pond = Pond.new(:maximum_size => 1) { 2 }
|
112
|
+
|
113
|
+
q1, q2, q3 = Queue.new, Queue.new, Queue.new
|
114
|
+
|
115
|
+
threads = 4.times.map do
|
116
|
+
Thread.new do
|
117
|
+
Thread.current[:value] = 0
|
118
|
+
|
119
|
+
q1.push nil
|
120
|
+
|
121
|
+
pond.checkout do |o|
|
122
|
+
Thread.current[:value] = o
|
123
|
+
q2.push nil
|
124
|
+
q3.pop
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
4.times { q1.pop }
|
130
|
+
q2.pop
|
131
|
+
|
132
|
+
threads.map{|t| t[:value]}.sort.should == [0, 0, 0, 2]
|
133
|
+
|
134
|
+
4.times { q3.push nil }
|
135
|
+
|
136
|
+
threads.each &:join
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should treat the collection of objects as a queue by default" do
|
140
|
+
int = 0
|
141
|
+
pond = Pond.new { int += 1 }
|
142
|
+
results = []
|
143
|
+
|
144
|
+
q = Queue.new
|
145
|
+
m = Mutex.new
|
146
|
+
cv = ConditionVariable.new
|
147
|
+
|
148
|
+
4.times do
|
149
|
+
threads = 4.times.map do
|
150
|
+
Thread.new do
|
151
|
+
m.synchronize do
|
152
|
+
pond.checkout do |i|
|
153
|
+
results << i
|
154
|
+
q.push nil
|
155
|
+
cv.wait(m)
|
156
|
+
end
|
157
|
+
cv.signal
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
4.times { q.pop }
|
163
|
+
cv.signal
|
164
|
+
threads.each(&:join)
|
165
|
+
end
|
166
|
+
|
167
|
+
pond.size.should == 4
|
168
|
+
results.should == (1..4).cycle(4).to_a
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should treat the collection of objects as a stack if configured that way" do
|
172
|
+
int = 0
|
173
|
+
pond = Pond.new(:collection => :stack) { int += 1 }
|
174
|
+
results = []
|
175
|
+
|
176
|
+
q = Queue.new
|
177
|
+
m = Mutex.new
|
178
|
+
cv = ConditionVariable.new
|
179
|
+
|
180
|
+
4.times do
|
181
|
+
threads = 4.times.map do
|
182
|
+
Thread.new do
|
183
|
+
m.synchronize do
|
184
|
+
pond.checkout do |i|
|
185
|
+
results << i
|
186
|
+
q.push nil
|
187
|
+
cv.wait(m)
|
188
|
+
end
|
189
|
+
cv.signal
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
4.times { q.pop }
|
195
|
+
cv.signal
|
196
|
+
threads.each(&:join)
|
197
|
+
end
|
198
|
+
|
199
|
+
pond.size.should == 4
|
200
|
+
results.should == [1, 2, 3, 4, 4, 3, 2, 1, 1, 2, 3, 4, 4, 3, 2, 1]
|
201
|
+
end
|
202
|
+
|
203
|
+
it "should raise a timeout error if it takes too long to return an object" do
|
204
|
+
pond = Pond.new(:timeout => 0.01, :maximum_size => 1){1}
|
205
|
+
|
206
|
+
q1, q2 = Queue.new, Queue.new
|
207
|
+
t = Thread.new do
|
208
|
+
pond.checkout do
|
209
|
+
q1.push nil
|
210
|
+
q2.pop
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
q1.pop
|
215
|
+
|
216
|
+
proc{pond.checkout{}}.should raise_error Pond::Timeout
|
217
|
+
|
218
|
+
q2.push nil
|
219
|
+
t.join
|
220
|
+
end
|
221
|
+
|
222
|
+
it "with a block that raises an error should check the object back in and propagate the error" do
|
223
|
+
pond = Pond.new { 1 }
|
224
|
+
proc do
|
225
|
+
pond.checkout do
|
226
|
+
raise "Blah!"
|
227
|
+
end
|
228
|
+
end.should raise_error RuntimeError, "Blah!"
|
229
|
+
|
230
|
+
pond.allocated.should == {}
|
231
|
+
pond.available.should == [1]
|
232
|
+
end
|
233
|
+
|
234
|
+
it "should not block other threads if the object instantiation takes a long time" do
|
235
|
+
t = nil
|
236
|
+
q1, q2, q3 = Queue.new, Queue.new, Queue.new
|
237
|
+
pond = Pond.new do
|
238
|
+
q1.push nil
|
239
|
+
q2.pop
|
240
|
+
end
|
241
|
+
|
242
|
+
q2.push 1
|
243
|
+
|
244
|
+
pond.checkout do |i|
|
245
|
+
q1.pop
|
246
|
+
i.should == 1
|
247
|
+
|
248
|
+
t = Thread.new do
|
249
|
+
pond.checkout do |i|
|
250
|
+
i.should == 2
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
q1.pop
|
255
|
+
end
|
256
|
+
|
257
|
+
pond.checkout { |i| i.should == 1 }
|
258
|
+
|
259
|
+
q2.push 2
|
260
|
+
t.join
|
261
|
+
end
|
262
|
+
|
263
|
+
it "should not leave the Pond in a bad state if object instantiation fails" do
|
264
|
+
int = 0
|
265
|
+
error = false
|
266
|
+
pond = Pond.new do
|
267
|
+
raise "Instantiation Error!" if error
|
268
|
+
int += 1
|
269
|
+
end
|
270
|
+
|
271
|
+
pond.checkout { |i| i.should == 1 }
|
272
|
+
|
273
|
+
pond.size.should == 1
|
274
|
+
pond.allocated.should == {}
|
275
|
+
pond.available.should == [1]
|
276
|
+
|
277
|
+
error = true
|
278
|
+
|
279
|
+
pond.checkout do |i|
|
280
|
+
i.should == 1
|
281
|
+
|
282
|
+
t = Thread.new do
|
283
|
+
pond.checkout{}
|
284
|
+
end
|
285
|
+
|
286
|
+
proc { t.join }.should raise_error RuntimeError, "Instantiation Error!"
|
287
|
+
end
|
288
|
+
|
289
|
+
pond.size.should == 1
|
290
|
+
pond.allocated.should == {}
|
291
|
+
pond.available.should == [1]
|
292
|
+
end
|
293
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pond, "configuration" do
|
4
|
+
it "should eagerly instantiate objects if the option is given" do
|
5
|
+
int = 0
|
6
|
+
pond = Pond.new(:eager => true){int += 1}
|
7
|
+
pond.available.should == (1..10).to_a
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should have its collection type gettable and settable" do
|
11
|
+
pond = Pond.new { Object.new }
|
12
|
+
pond.collection.should == :queue
|
13
|
+
pond.collection = :stack
|
14
|
+
pond.collection.should == :stack
|
15
|
+
|
16
|
+
pond = Pond.new(:collection => :stack) { Object.new }
|
17
|
+
pond.collection.should == :stack
|
18
|
+
pond.collection = :queue
|
19
|
+
pond.collection.should == :queue
|
20
|
+
|
21
|
+
procs = [
|
22
|
+
proc{pond.collection = nil},
|
23
|
+
proc{Pond.new(:collection => nil) { Object.new }},
|
24
|
+
proc{pond.collection = :blah},
|
25
|
+
proc{Pond.new(:collection => :blah) { Object.new }}
|
26
|
+
]
|
27
|
+
|
28
|
+
procs.each { |p| p.should raise_error RuntimeError, /Bad value for Pond collection:/ }
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should have its timeout gettable and settable" do
|
32
|
+
pond = Pond.new { Object.new }
|
33
|
+
pond.timeout.should == 1
|
34
|
+
pond.timeout = 4
|
35
|
+
pond.timeout.should == 4
|
36
|
+
|
37
|
+
pond = Pond.new(:timeout => 3.7) { Object.new }
|
38
|
+
pond.timeout.should == 3.7
|
39
|
+
pond.timeout = 1.9
|
40
|
+
pond.timeout.should == 1.9
|
41
|
+
|
42
|
+
procs = [
|
43
|
+
proc{pond.timeout = nil},
|
44
|
+
proc{Pond.new(:timeout => nil) { Object.new }},
|
45
|
+
proc{pond.timeout = :blah},
|
46
|
+
proc{Pond.new(:timeout => :blah) { Object.new }}
|
47
|
+
]
|
48
|
+
|
49
|
+
procs.each { |p| p.should raise_error RuntimeError, /Bad value for Pond timeout:/ }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should have its maximum_size gettable and settable" do
|
53
|
+
pond = Pond.new { Object.new }
|
54
|
+
pond.maximum_size.should == 10
|
55
|
+
pond.maximum_size = 7
|
56
|
+
pond.maximum_size.should == 7
|
57
|
+
pond.maximum_size = 0
|
58
|
+
pond.maximum_size.should == 0
|
59
|
+
pond.maximum_size = 2
|
60
|
+
pond.maximum_size.should == 2
|
61
|
+
|
62
|
+
procs = [
|
63
|
+
proc{pond.maximum_size = nil},
|
64
|
+
proc{Pond.new(:maximum_size => nil) { Object.new }},
|
65
|
+
proc{pond.maximum_size = :blah},
|
66
|
+
proc{Pond.new(:maximum_size => :blah) { Object.new }},
|
67
|
+
proc{pond.maximum_size = 4.0},
|
68
|
+
proc{Pond.new(:maximum_size => 4.0) { Object.new }},
|
69
|
+
]
|
70
|
+
|
71
|
+
procs.each { |p| p.should raise_error RuntimeError, /Bad value for Pond maximum_size:/ }
|
72
|
+
end
|
73
|
+
|
74
|
+
it "when the maximum_size is decreased should free available objects" do
|
75
|
+
int = 0
|
76
|
+
pond = Pond.new(:eager => true) { int += 1 }
|
77
|
+
|
78
|
+
pond.available.should == (1..10).to_a
|
79
|
+
pond.maximum_size = 8
|
80
|
+
pond.available.should == (3..10).to_a
|
81
|
+
pond.maximum_size = 10
|
82
|
+
pond.available.should == (3..10).to_a
|
83
|
+
pond.maximum_size = 9
|
84
|
+
pond.available.should == (3..10).to_a
|
85
|
+
end
|
86
|
+
|
87
|
+
it "when the maximum_size is decreased should free available objects and checked-out objects upon return" do
|
88
|
+
int = 0
|
89
|
+
pond = Pond.new(:eager => true, :maximum_size => 2) { int += 1 }
|
90
|
+
pond.available.should == [1, 2]
|
91
|
+
|
92
|
+
q1, q2 = Queue.new, Queue.new
|
93
|
+
t = Thread.new do
|
94
|
+
pond.checkout do |i|
|
95
|
+
i.should == 1
|
96
|
+
q1.push nil
|
97
|
+
q2.pop
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
q1.pop
|
102
|
+
|
103
|
+
pond.maximum_size = 0
|
104
|
+
pond.maximum_size.should == 0
|
105
|
+
|
106
|
+
pond.size.should == 1
|
107
|
+
pond.available.should == []
|
108
|
+
pond.allocated.should == {t => 1}
|
109
|
+
|
110
|
+
q2.push nil
|
111
|
+
t.join
|
112
|
+
|
113
|
+
pond.size.should == 0
|
114
|
+
pond.available.should == []
|
115
|
+
pond.allocated.should == {}
|
116
|
+
end
|
117
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'pond'
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pond::Wrapper do
|
4
|
+
class Wrapped
|
5
|
+
def pipelined(&block)
|
6
|
+
yield
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
@wrapper = Pond.wrap { Wrapped.new }
|
12
|
+
@pond = @wrapper.pond
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should proxy method calls to checked out objects" do
|
16
|
+
@pond.size.should == 0
|
17
|
+
|
18
|
+
@wrapper.class.should == Wrapped
|
19
|
+
@wrapper.respond_to?(:pipelined).should == true
|
20
|
+
object_id = @wrapper.object_id
|
21
|
+
|
22
|
+
@pond.size.should == 1
|
23
|
+
@pond.allocated.should == {}
|
24
|
+
@pond.available.map(&:object_id).should == [object_id]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return the same object within a block passed to one of its methods" do
|
28
|
+
q1, q2 = Queue.new, Queue.new
|
29
|
+
oid1, oid2 = nil, nil
|
30
|
+
|
31
|
+
@wrapper.pipelined do
|
32
|
+
oid1 = @wrapper.object_id
|
33
|
+
|
34
|
+
t = Thread.new do
|
35
|
+
@wrapper.pipelined do
|
36
|
+
q1.push nil
|
37
|
+
q2.pop
|
38
|
+
|
39
|
+
oid2 = @wrapper.object_id
|
40
|
+
oid2.should == @wrapper.object_id
|
41
|
+
@wrapper
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
q1.pop
|
46
|
+
|
47
|
+
@wrapper.object_id.should == oid1
|
48
|
+
|
49
|
+
@pond.allocated.keys.should == [Thread.current, t]
|
50
|
+
@pond.available.should == []
|
51
|
+
|
52
|
+
q2.push nil
|
53
|
+
t.join
|
54
|
+
|
55
|
+
@wrapper.object_id.should == oid1
|
56
|
+
@wrapper.object_id.should == oid1
|
57
|
+
end
|
58
|
+
|
59
|
+
@pond.allocated.should == {}
|
60
|
+
@pond.available.map(&:object_id).should == [oid2, oid1]
|
61
|
+
end
|
62
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pond
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Chris Hanks
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2014-02-15 00:00:00 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
prerelease: false
|
17
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: "1.3"
|
22
|
+
type: :development
|
23
|
+
version_requirements: *id001
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: rspec
|
26
|
+
prerelease: false
|
27
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "2.14"
|
32
|
+
type: :development
|
33
|
+
version_requirements: *id002
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rake
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- &id004
|
40
|
+
- ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: "0"
|
43
|
+
type: :development
|
44
|
+
version_requirements: *id003
|
45
|
+
description: A simple, generic, thread-safe pool for connections or whatever else
|
46
|
+
email:
|
47
|
+
- christopher.m.hanks@gmail.com
|
48
|
+
executables: []
|
49
|
+
|
50
|
+
extensions: []
|
51
|
+
|
52
|
+
extra_rdoc_files: []
|
53
|
+
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- .rspec
|
57
|
+
- CHANGELOG.md
|
58
|
+
- Gemfile
|
59
|
+
- LICENSE.txt
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- lib/pond.rb
|
63
|
+
- lib/pond/version.rb
|
64
|
+
- pond.gemspec
|
65
|
+
- spec/checkout_spec.rb
|
66
|
+
- spec/config_spec.rb
|
67
|
+
- spec/spec_helper.rb
|
68
|
+
- spec/wrapper_spec.rb
|
69
|
+
homepage: https://github.com/chanks/pond
|
70
|
+
licenses:
|
71
|
+
- MIT
|
72
|
+
metadata: {}
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- *id004
|
82
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- *id004
|
85
|
+
requirements: []
|
86
|
+
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 2.2.2
|
89
|
+
signing_key:
|
90
|
+
specification_version: 4
|
91
|
+
summary: A simple, generic, thread-safe pool
|
92
|
+
test_files:
|
93
|
+
- spec/checkout_spec.rb
|
94
|
+
- spec/config_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
- spec/wrapper_spec.rb
|