pond 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --order random
@@ -0,0 +1,3 @@
1
+ ### 0.1.0 (2014-02-14)
2
+
3
+ * Initial release.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pond.gemspec
4
+ gemspec
5
+
6
+ gem 'rubysl', '~> 2.0', :platform => :rbx
@@ -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.
@@ -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.
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new :default do |spec|
6
+ spec.pattern = './spec/**/*_spec.rb'
7
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ class Pond
2
+ VERSION = '0.1.0'
3
+ end
@@ -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
@@ -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
@@ -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