gene_pool 1.0.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/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ doc
2
+ pkg
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 1.0.0 / 2010-09-05
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Brad Pardee
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,37 @@
1
+ = gene_pool
2
+
3
+ * http://github.com/bpardee/gene_pool
4
+
5
+ == DESCRIPTION:
6
+
7
+ Generic pooling library for connection pools.
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * Thread-safe
12
+ * Pure ruby
13
+
14
+ == INSTALL:
15
+
16
+ gem install gene_pool
17
+
18
+ == EXAMPLE USAGE:
19
+
20
+ class MyClient
21
+ @@gene_pool = GenePool.new(:name => 'MyClient',
22
+ :pool_size => 10,
23
+ :warn_timeout => 0.25,
24
+ :logger => Rails.logger) do
25
+ TCPSocket.new('myserver', 4321)
26
+ end
27
+
28
+ def send_message
29
+ @@gene_pool.with_connection do |socket|
30
+ # use socket here
31
+ end
32
+ end
33
+ end
34
+
35
+ == Copyright
36
+
37
+ Copyright (c) 2010 Brad Pardee. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = "gene_pool"
8
+ gemspec.summary = "Generic pooling library for creating a connection pool"
9
+ gemspec.description = "Generic pooling library for creating a connection pool"
10
+ gemspec.email = "bradpardee@gmail.com"
11
+ gemspec.homepage = "http://github.com/bpardee/gene_pool"
12
+ gemspec.authors = ["Brad Pardee"]
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: gem install jeweler"
16
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/gene_pool.gemspec ADDED
@@ -0,0 +1,49 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{gene_pool}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Brad Pardee"]
12
+ s.date = %q{2010-09-08}
13
+ s.description = %q{Generic pooling library for creating a connection pool}
14
+ s.email = %q{bradpardee@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "History.txt",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "gene_pool.gemspec",
27
+ "lib/gene_pool.rb",
28
+ "test/gene_pool_test.rb"
29
+ ]
30
+ s.homepage = %q{http://github.com/bpardee/gene_pool}
31
+ s.rdoc_options = ["--charset=UTF-8"]
32
+ s.require_paths = ["lib"]
33
+ s.rubygems_version = %q{1.3.6}
34
+ s.summary = %q{Generic pooling library for creating a connection pool}
35
+ s.test_files = [
36
+ "test/gene_pool_test.rb"
37
+ ]
38
+
39
+ if s.respond_to? :specification_version then
40
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
41
+ s.specification_version = 3
42
+
43
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
44
+ else
45
+ end
46
+ else
47
+ end
48
+ end
49
+
data/lib/gene_pool.rb ADDED
@@ -0,0 +1,134 @@
1
+ # Generic connection pool class
2
+ class GenePool
3
+
4
+ attr_reader :name, :pool_size, :warn_timeout, :logger
5
+
6
+ def initialize(options={}, &connect_block)
7
+ @connect_block = connect_block
8
+
9
+ @name = options[:name] || 'GenePool'
10
+ @pool_size = options[:pool_size] || 1
11
+ @warn_timeout = options[:warn_timeout] || 5.0
12
+ @logger = options[:logger]
13
+
14
+ # Mutex for synchronizing pool access
15
+ @mutex = Mutex.new
16
+
17
+ # Condition variable for waiting for an available connection
18
+ @queue = ConditionVariable.new
19
+
20
+ @connections = []
21
+ @checked_out = []
22
+ # Map the original connections object_id within the with_connection method to the final connection.
23
+ # This could change if the connection is renew'ed.
24
+ @with_map = {}
25
+ end
26
+
27
+ # Check out a connection from the pool, creating it if necessary.
28
+ def checkout
29
+ start_time = Time.now
30
+ connection = nil
31
+ reserved_connection_placeholder = Thread.current
32
+ begin
33
+ @mutex.synchronize do
34
+ until connection do
35
+ if @checked_out.size < @connections.size
36
+ connection = (@connections - @checked_out).first
37
+ @checked_out << connection
38
+ elsif @connections.size < @pool_size
39
+ # Perform the actual connection outside the mutex
40
+ connection = reserved_connection_placeholder
41
+ @connections << connection
42
+ @checked_out << connection
43
+ @logger.debug "#{@name}: Created connection ##{@connections.size} #{connection}:#{connection.object_id} for #{name}" if @logger && @logger.debug?
44
+ else
45
+ @logger.info "#{@name}: Waiting for an available connection, all #{@pool_size} connections are checked out." if @logger
46
+ @queue.wait(@mutex)
47
+ end
48
+ end
49
+ end
50
+ ensure
51
+ delta = Time.now - start_time
52
+ if @logger && delta > @warn_timeout
53
+ @logger.warn "#{@name}: It took #{delta} seconds to obtain a connection. Consider raising the pool size which is " +
54
+ "currently set to #{@pool_size}."
55
+ end
56
+ end
57
+ if connection == reserved_connection_placeholder
58
+ connection = renew(reserved_connection_placeholder)
59
+ end
60
+
61
+ @logger.debug "#{@name}: Checkout connection #{connection.object_id} self=#{self}" if @logger && @logger.debug?
62
+ return connection
63
+ end
64
+
65
+ # Return a connection to the pool.
66
+ def checkin(connection)
67
+ @mutex.synchronize do
68
+ @checked_out.delete(connection)
69
+ @queue.signal
70
+ end
71
+ @logger.debug "#{@name}: Checkin connection #{connection.object_id} self=#{self}" if @logger && @logger.debug?
72
+ end
73
+
74
+ # Create a scope for checking out a connection
75
+ def with_connection
76
+ connection = checkout
77
+ @mutex.synchronize do
78
+ @with_map[connection.object_id] = connection
79
+ end
80
+ begin
81
+ yield connection
82
+ ensure
83
+ @mutex.synchronize do
84
+ # Update connection for any renew's that have occurred
85
+ connection = @with_map.delete(connection.object_id)
86
+ end
87
+ checkin(connection)
88
+ end
89
+ end
90
+
91
+ # Remove an existing connection from the pool
92
+ def remove(connection)
93
+ @mutex.synchronize do
94
+ @connections.delete(connection)
95
+ @checked_out.delete(connection)
96
+ @queue.signal
97
+ end
98
+ @logger.debug "#{@name}: Removed connection #{connection.object_id} self=#{self}" if @logger && @logger.debug?
99
+ end
100
+
101
+ # If a connection needs to be renewed for some reason, reassign it here
102
+ def renew(old_connection)
103
+ new_connection =
104
+ begin
105
+ @connect_block.call
106
+ rescue Exception
107
+ remove old_connection
108
+ raise
109
+ end
110
+ @mutex.synchronize do
111
+ index = @checked_out.index(old_connection)
112
+ raise Error.new("Can't reassign non-checked out connection for #{@name}") unless index
113
+ @checked_out[index] = new_connection
114
+ @connections[@connections.index(old_connection)] = new_connection
115
+ # If this is part of a with_connection block, then track our new connection
116
+ with_key = @with_map.index(old_connection)
117
+ @with_map[with_key] = new_connection if with_key
118
+ end
119
+ @logger.debug "#{@name}: Renewed connection old=#{old_connection.object_id} new=#{new_connection}:#{new_connection.object_id}" if @logger && @logger.debug?
120
+ return new_connection
121
+ end
122
+
123
+ # Perform the given block for each connection, i.e., closing each connection.
124
+ def each
125
+ @connections.each { |connection| yield connection }
126
+ end
127
+
128
+ def to_s
129
+ conn = @connections.map{|c| c.object_id}.join(',')
130
+ chk = @checked_out.map{|c| c.object_id}.join(',')
131
+ with = @with_map.keys.map{|k| "#{k}=#{@with_map[k].object_id}"}.join(',')
132
+ "connections=#{conn} checked_out=#{chk} with_map=#{with}"
133
+ end
134
+ end
@@ -0,0 +1,232 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'gene_pool'
5
+ require 'stringio'
6
+ require 'logger'
7
+ require 'timeout'
8
+
9
+ # Increase visibility
10
+ class GenePool
11
+ attr_reader :connections, :checked_out, :with_map
12
+ end
13
+
14
+ class DummyConnection
15
+ def initialize(count, sleep_time=nil)
16
+ sleep sleep_time if sleep_time
17
+ @count = count
18
+ end
19
+
20
+ def to_i
21
+ @count
22
+ end
23
+
24
+ def to_s
25
+ @count.to_s
26
+ end
27
+ end
28
+
29
+
30
+ class GenePoolTest < Test::Unit::TestCase
31
+
32
+ context 'on default setup' do
33
+ setup do
34
+ @gene_pool = GenePool.new { Object.new }
35
+ end
36
+
37
+ should 'have default options set' do
38
+ assert_equal 'GenePool', @gene_pool.name
39
+ assert_equal 1, @gene_pool.pool_size
40
+ assert_equal 5.0, @gene_pool.warn_timeout
41
+ assert_nil @gene_pool.logger
42
+ end
43
+ end
44
+
45
+ context '' do
46
+ setup do
47
+ #@stringio = StringIO.new
48
+ #@logger = Logger.new($stdout)
49
+ #@logger = Logger.new(@stringio)
50
+ @logger = nil
51
+ # Override sleep in individual tests
52
+ @sleep = nil
53
+ @timeout = 5
54
+ counter = 0
55
+ mutex = Mutex.new
56
+ @gene_pool = GenePool.new(:name => 'TestGenePool',
57
+ :pool_size => 10,
58
+ :warn_timeout => 2.0,
59
+ :logger => @logger) do
60
+ count = nil
61
+ mutex.synchronize do
62
+ count = counter += 1
63
+ end
64
+ Timeout.timeout(@timeout) do
65
+ DummyConnection.new(count, @sleep)
66
+ end
67
+ end
68
+ end
69
+
70
+ should 'have options set' do
71
+ assert_equal 'TestGenePool', @gene_pool.name
72
+ assert_equal 10, @gene_pool.pool_size
73
+ assert_equal 2.0, @gene_pool.warn_timeout
74
+ assert_same @logger, @gene_pool.logger
75
+ end
76
+
77
+ should 'create 1 connection' do
78
+ (1..3).each do |i|
79
+ @gene_pool.with_connection do |conn|
80
+ assert_equal conn.to_i, 1
81
+ assert_equal 1, @gene_pool.connections.size
82
+ assert_equal 1, @gene_pool.checked_out.size
83
+ assert_same conn, @gene_pool.connections[0]
84
+ assert_same conn, @gene_pool.checked_out[0]
85
+ end
86
+ assert_equal 0, @gene_pool.checked_out.size
87
+ end
88
+ end
89
+
90
+ should 'create 2 connections' do
91
+ conn1 = @gene_pool.checkout
92
+ (1..3).each do |i|
93
+ @gene_pool.with_connection do |conn2|
94
+ assert_equal 1, conn1.to_i
95
+ assert_equal 2, conn2.to_i
96
+ assert_equal 2, @gene_pool.connections.size
97
+ assert_equal 2, @gene_pool.checked_out.size
98
+ assert_same conn1, @gene_pool.connections[0]
99
+ assert_same conn1, @gene_pool.checked_out[0]
100
+ assert_same conn2, @gene_pool.connections[1]
101
+ assert_same conn2, @gene_pool.checked_out[1]
102
+ end
103
+ assert_equal 1, @gene_pool.checked_out.size
104
+ end
105
+ @gene_pool.checkin(conn1)
106
+ assert_equal 0, @gene_pool.checked_out.size
107
+ end
108
+
109
+ should 'be able to reset multiple times' do
110
+ @gene_pool.with_connection do |conn1|
111
+ conn2 = @gene_pool.renew(conn1)
112
+ conn3 = @gene_pool.renew(conn2)
113
+ assert_equal 1, conn1.to_i
114
+ assert_equal 2, conn2.to_i
115
+ assert_equal 3, conn3.to_i
116
+ assert_equal 1, @gene_pool.connections.size
117
+ assert_equal 1, @gene_pool.checked_out.size
118
+ end
119
+ assert_equal 1, @gene_pool.connections.size
120
+ assert_equal 0, @gene_pool.checked_out.size
121
+ end
122
+
123
+ should 'be able to remove connection' do
124
+ @gene_pool.with_connection do |conn|
125
+ @gene_pool.remove(conn)
126
+ assert_equal 0, @gene_pool.connections.size
127
+ assert_equal 0, @gene_pool.checked_out.size
128
+ end
129
+ assert_equal 0, @gene_pool.connections.size
130
+ assert_equal 0, @gene_pool.checked_out.size
131
+ end
132
+
133
+ should 'be able to remove multiple connections' do
134
+ @gene_pool.with_connection do |conn1|
135
+ @gene_pool.with_connection do |conn2|
136
+ @gene_pool.with_connection do |conn3|
137
+ @gene_pool.remove(conn1)
138
+ @gene_pool.remove(conn3)
139
+ assert_equal 1, @gene_pool.connections.size
140
+ assert_equal 1, @gene_pool.checked_out.size
141
+ assert_same conn2, @gene_pool.checked_out[0]
142
+ assert_same conn2, @gene_pool.connections[0]
143
+ end
144
+ assert_equal 1, @gene_pool.connections.size
145
+ assert_equal 1, @gene_pool.checked_out.size
146
+ end
147
+ assert_equal 1, @gene_pool.connections.size
148
+ assert_equal 0, @gene_pool.checked_out.size
149
+ end
150
+ assert_equal 1, @gene_pool.connections.size
151
+ assert_equal 0, @gene_pool.checked_out.size
152
+ end
153
+
154
+ should 'handle aborted connection' do
155
+ @gene_pool.with_connection do |conn1|
156
+ @sleep = 2
157
+ @timeout = 1
158
+ begin
159
+ @gene_pool.with_connection { |conn2| }
160
+ flunk "connection should have timed out"
161
+ rescue Timeout::Error => e
162
+ #pass
163
+ end
164
+ assert_equal 1, @gene_pool.connections.size
165
+ assert_equal 1, @gene_pool.checked_out.size
166
+ end
167
+ assert_equal 1, @gene_pool.connections.size
168
+ assert_equal 0, @gene_pool.checked_out.size
169
+ # Do another test just to be sure nothings hosed
170
+ @sleep = nil
171
+ @gene_pool.with_connection do |conn1|
172
+ assert 1, conn1.to_i
173
+ end
174
+ end
175
+
176
+ should 'not allow more than pool_size connections' do
177
+ conns = []
178
+ pool_size = @gene_pool.pool_size
179
+ (1..pool_size).each do |i|
180
+ c = @gene_pool.checkout
181
+ conns << c
182
+ assert_equal i, c.to_i
183
+ assert_equal i, @gene_pool.connections.size
184
+ assert_equal i, @gene_pool.checked_out.size
185
+ assert_equal conns, @gene_pool.connections
186
+ end
187
+ begin
188
+ Timeout.timeout(1) do
189
+ @gene_pool.checkout
190
+ end
191
+ flunk "connection should have timed out"
192
+ rescue Timeout::Error => e
193
+ #pass "successfully timed out connection"
194
+ end
195
+ (1..pool_size).each do |i|
196
+ @gene_pool.checkin(conns[i-1])
197
+ assert_equal pool_size, @gene_pool.connections.size
198
+ assert_equal pool_size-i, @gene_pool.checked_out.size
199
+ end
200
+ end
201
+
202
+ should 'handle thread contention' do
203
+ conns = []
204
+ pool_size = @gene_pool.pool_size
205
+ # Do it with new connections and old connections
206
+ (1..2).each do |n|
207
+ (1..pool_size).each do |i|
208
+ Thread.new do
209
+ c = @gene_pool.checkout
210
+ conns[i-1] = c
211
+ end
212
+ end
213
+ # Let the threads complete
214
+ sleep 1
215
+ assert_equal pool_size, @gene_pool.connections.size
216
+ assert_equal pool_size, @gene_pool.checked_out.size
217
+ (1..pool_size).each do |i|
218
+ Thread.new do
219
+ @gene_pool.checkin(conns[i-1])
220
+ end
221
+ end
222
+ sleep 1
223
+ assert_equal pool_size, @gene_pool.connections.size
224
+ assert_equal 0, @gene_pool.checked_out.size
225
+ end
226
+ ival_conns = []
227
+ @gene_pool.each { |conn| ival_conns << conn.to_i }
228
+ ival_conns.sort!
229
+ assert_equal (1..pool_size).to_a, ival_conns
230
+ end
231
+ end
232
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gene_pool
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 1
7
+ - 0
8
+ - 0
9
+ version: 1.0.0
10
+ platform: ruby
11
+ authors:
12
+ - Brad Pardee
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-09-08 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies: []
20
+
21
+ description: Generic pooling library for creating a connection pool
22
+ email: bradpardee@gmail.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - LICENSE
29
+ - README.rdoc
30
+ files:
31
+ - .gitignore
32
+ - History.txt
33
+ - LICENSE
34
+ - README.rdoc
35
+ - Rakefile
36
+ - VERSION
37
+ - gene_pool.gemspec
38
+ - lib/gene_pool.rb
39
+ - test/gene_pool_test.rb
40
+ has_rdoc: true
41
+ homepage: http://github.com/bpardee/gene_pool
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options:
46
+ - --charset=UTF-8
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ segments:
54
+ - 0
55
+ version: "0"
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.6
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Generic pooling library for creating a connection pool
70
+ test_files:
71
+ - test/gene_pool_test.rb