generic_connection_pool 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Kame Chen
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 ADDED
@@ -0,0 +1,47 @@
1
+ = generic_connection_pool
2
+
3
+ == DESCRIPTION
4
+ Always network clients require a connection pool, like database connection, cache connection and others.
5
+ Generic connection pool can be used with anything. It is inspired from ActiveRecord ConnectionPool.
6
+ Sharing a limited number of network connections among many threads.
7
+ Connections are created delayed.
8
+
9
+ == USEAGE
10
+
11
+ Connections can be obtained and used from a connection pool in several
12
+ ways:
13
+
14
+ 1. Use connection_pool.connection to obtain a connection instance.
15
+ and when you're done with the connection(s) and wish it to be returned
16
+ to the pool, you can call connection_pool.release_connection to release
17
+ connection back to the connection pool.
18
+ 2. Manually check out a connection from the pool with connection_pool.checkout.
19
+ You are responsible for returning this connection to the pool when
20
+ finished by calling connection_pool.checkin(connection).
21
+ 3. Use connection_pool.with_connection(&block), which obtains a connection,
22
+ yields it as the sole argument to the block, and returns it to the pool
23
+ after the block completes.
24
+
25
+ Connections in the pool may be Adapter instance that should implete methods like
26
+ verify!, disconnect!, active? etc.
27
+
28
+ == Example
29
+ require 'generic_connection_pool'
30
+ connection_pool = ConnectionPool.new(:size => 5, :timeout => 5) do
31
+ new_connection_adapter
32
+ end
33
+ connection_pool.connection
34
+ connection_pool.release_connection
35
+
36
+ connection = connection_pool.checkout
37
+ connection_pool.checkin(connection)
38
+
39
+ connection.with_connection{|connection| do_somting_with_connection}
40
+
41
+ Do something in the block, that always create an adapter instance or connect to a server.
42
+
43
+ == INSTALL
44
+ gem install generic_connection_pool
45
+
46
+ == AUTHOR
47
+ Kame Chen
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'spec'
7
+ require 'spec/rake/spectask'
8
+
9
+ desc 'Default: run test.'
10
+ task :default => :spec
11
+
12
+ desc 'Test the ao_locked gem.'
13
+ Spec::Rake::SpecTask.new(:spec) do |t|
14
+ t.spec_files = FileList['spec/**/*_spec.rb']
15
+ t.spec_opts = ['-c','-f','nested']
16
+ end
17
+
18
+ desc 'Generate documentation for the ao_locked gem.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'ConnectionPool'
22
+ rdoc.options << '--line-numbers' << '--inline-source'
23
+ rdoc.rdoc_files.include('README')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ spec = eval(File.read(File.expand_path("../generic_connection_pool.gemspec", __FILE__)))
28
+ Rake::GemPackageTask.new(spec) do |pkg|
29
+ pkg.need_zip = false
30
+ pkg.need_tar = false
31
+ end
@@ -0,0 +1,247 @@
1
+ require 'rubygems'
2
+ require 'active_support/core_ext/module/synchronization'
3
+ require 'monitor'
4
+ require 'set'
5
+ require 'timeout'
6
+ # Generic connection pool class inspired from ActiveRecord ConnectionPool.
7
+ # Sharing a limited number of network connections among many threads.
8
+ # Connections are created delayed.
9
+ #
10
+ # == Introduction
11
+ #
12
+ # A connection pool synchronizes thread access to a limited number of
13
+ # network connections. The basic idea is that each thread checks out a
14
+ # database connection from the pool, uses that connection, and checks the
15
+ # connection back in. ConnectionPool is completely thread-safe, and will
16
+ # ensure that a connection cannot be used by two threads at the same time,
17
+ # as long as ConnectionPool's contract is correctly followed. It will also
18
+ # handle cases in which there are more threads than connections: if all
19
+ # connections have been checked out, and a thread tries to checkout a
20
+ # connection anyway, then ConnectionPool will wait until some other thread
21
+ # has checked in a connection.
22
+ #
23
+ # == Obtaining (checking out) a connection
24
+ #
25
+ # Connections can be obtained and used from a connection pool in several
26
+ # ways:
27
+ #
28
+ # 1. Use connection_pool.connection to obtain a connection instance.
29
+ # and when you're done with the connection(s) and wish it to be returned
30
+ # to the pool, you can call connection_pool.release_connection to release
31
+ # connection back to the connection pool.
32
+ # 2. Manually check out a connection from the pool with connection_pool.checkout.
33
+ # You are responsible for returning this connection to the pool when
34
+ # finished by calling connection_pool.checkin(connection).
35
+ # 3. Use connection_pool.with_connection(&block), which obtains a connection,
36
+ # yields it as the sole argument to the block, and returns it to the pool
37
+ # after the block completes.
38
+ #
39
+ # Connections in the pool may be Adapter instance that should implete methods like
40
+ # verify!, disconnect!, active? etc.
41
+ #
42
+ # == Example
43
+ #
44
+ # connection_pool = ConnectionPool.new(:size => 5, :timeout => 5) do
45
+ # new_connection_adapter
46
+ # end
47
+ # connection_pool.connection
48
+ # connection_pool.release_connection
49
+ #
50
+ # connection = connection_pool.checkout
51
+ # connection_pool.checkin(connection)
52
+ #
53
+ # connection.with_connection{|connection| do_somting_with_connection}
54
+ #
55
+ #
56
+ # Do something in the block, that always create an adapter instance or connect to a server.
57
+ #
58
+ class ConnectionPool
59
+ VERSION = '0.1.0'
60
+
61
+ class ConnectionTimeoutError < ::Timeout::Error
62
+ end
63
+
64
+ attr_reader :options
65
+
66
+ DEFAULT_OPTIONS = { :size => 5, :timeout => 5 }
67
+
68
+
69
+
70
+ # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
71
+ # object which describes database connection information (e.g. adapter,
72
+ # host name, username, password, etc), as well as the maximum size for
73
+ # this ConnectionPool.
74
+ #
75
+ # The default ConnectionPool maximum size is 5.
76
+ def initialize(options = {}, &block)
77
+ @options = DEFAULT_OPTIONS.merge(options)
78
+
79
+ raise ArgumentError, "Connection pool requires a block that create a new connection!" unless block
80
+
81
+ @connection_block = block
82
+
83
+ # The cache of reserved connections mapped to threads
84
+ @reserved_connections = {}
85
+
86
+ # The mutex used to synchronize pool access
87
+ @connection_mutex = Monitor.new
88
+ @queue = @connection_mutex.new_cond
89
+
90
+ # default 5 second timeout
91
+ @timeout = @options[:timeout]
92
+
93
+ # default max pool size to 5
94
+ @size = @options[:size]
95
+
96
+ @connections = []
97
+ @checked_out = []
98
+ end
99
+
100
+ # Retrieve the connection associated with the current thread, or call
101
+ # #checkout to obtain one if necessary.
102
+ #
103
+ # #connection can be called any number of times; the connection is
104
+ # held in a hash keyed by the thread id.
105
+ def connection
106
+ @reserved_connections[current_connection_id] ||= checkout
107
+ end
108
+
109
+ # Signal that the thread is finished with the current connection.
110
+ # #release_connection releases the connection-thread association
111
+ # and returns the connection to the pool.
112
+ def release_connection
113
+ conn = @reserved_connections.delete(current_connection_id)
114
+ checkin conn if conn
115
+ end
116
+
117
+ # If a connection already exists yield it to the block. If no connection
118
+ # exists checkout a connection, yield it to the block, and checkin the
119
+ # connection when finished.
120
+ def with_connection
121
+ fresh_connection = true unless connection_cached?
122
+ yield connection
123
+ ensure
124
+ release_connection if fresh_connection
125
+ end
126
+
127
+ # Returns true if a connection has already been opened.
128
+ def connected?
129
+ !@connections.empty?
130
+ end
131
+
132
+ # Disconnects all connections in the pool, and clears the pool.
133
+ def disconnect!
134
+ @reserved_connections.each do |name,conn|
135
+ checkin conn
136
+ end
137
+ @reserved_connections = {}
138
+ @connections.each do |conn|
139
+ conn.disconnect!
140
+ end
141
+ @connections = []
142
+ end
143
+
144
+ # Verify active connections and remove and disconnect connections
145
+ # associated with stale threads.
146
+ def verify_active_connections! #:nodoc:
147
+ clear_stale_cached_connections!
148
+ @connections.each do |connection|
149
+ connection.verify!
150
+ end
151
+ end
152
+
153
+ # Return any checked-out connections back to the pool by threads that
154
+ # are no longer alive.
155
+ def clear_stale_cached_connections!
156
+ keys = @reserved_connections.keys - Thread.list.find_all { |t|
157
+ t.alive?
158
+ }.map { |thread| thread.object_id }
159
+ keys.each do |key|
160
+ checkin @reserved_connections[key]
161
+ @reserved_connections.delete(key)
162
+ end
163
+ end
164
+
165
+ # Check-out a database connection from the pool, indicating that you want
166
+ # to use it. You should call #checkin when you no longer need this.
167
+ #
168
+ # This is done by either returning an existing connection, or by creating
169
+ # a new connection. If the maximum number of connections for this pool has
170
+ # already been reached, but the pool is empty (i.e. they're all being used),
171
+ # then this method will wait until a thread has checked in a connection.
172
+ # The wait time is bounded however: if no connection can be checked out
173
+ # within the timeout specified for this pool, then a ConnectionTimeoutError
174
+ # exception will be raised.
175
+ #
176
+ # Returns: connection instance return by the connection_block
177
+ #
178
+ def checkout
179
+ # Checkout an available connection
180
+ @connection_mutex.synchronize do
181
+ loop do
182
+ conn = if @checked_out.size < @connections.size
183
+ checkout_existing_connection
184
+ elsif @connections.size < @size
185
+ checkout_new_connection
186
+ end
187
+ return conn if conn
188
+ # No connections available; wait for one
189
+ if @queue.wait(@timeout)
190
+ next
191
+ else
192
+ # try looting dead threads
193
+ clear_stale_cached_connections!
194
+ if @size == @checked_out.size
195
+ raise ConnectionTimeoutError, "Could not obtain a connection within #{@timeout} seconds. The max pool size is currently #{@size}; consider increasing it."
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ # Check-in a connection back into the pool, indicating that you
203
+ # no longer need this connection.
204
+ #
205
+ # +conn+: which was obtained by earlier by calling +checkout+ on this pool.
206
+ def checkin(conn)
207
+ @connection_mutex.synchronize do
208
+ @checked_out.delete conn
209
+ @queue.signal
210
+ end
211
+ end
212
+
213
+ # whether connection cached in the current thread
214
+ def connection_cached?
215
+ !!@reserved_connections[current_connection_id]
216
+ end
217
+
218
+ synchronize :verify_active_connections!, :connected?, :disconnect!, :with => :@connection_mutex
219
+
220
+ private
221
+ def new_connection
222
+ @connection_block.call
223
+ end
224
+
225
+ def current_connection_id #:nodoc:
226
+ Thread.current.object_id
227
+ end
228
+
229
+ def checkout_new_connection
230
+ c = new_connection
231
+ @connections << c
232
+ checkout_and_verify(c)
233
+ end
234
+
235
+ def checkout_existing_connection
236
+ c = (@connections - @checked_out).first
237
+ checkout_and_verify(c)
238
+ end
239
+
240
+ def checkout_and_verify(c)
241
+ # connection must have verify! method that verify the connection active;
242
+ # it should auto retry to reconnect if not active.
243
+ c.verify!
244
+ @checked_out << c
245
+ c
246
+ end
247
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path('../connection_pool', __FILE__)
@@ -0,0 +1,98 @@
1
+ require File.expand_path("../", __FILE__) + '/spec_helper'
2
+
3
+ def mock_connection_adapter
4
+ mock_connection_adapter = mock("connection_adapter")
5
+ mock_connection_adapter.stub!(:verify!)
6
+ mock_connection_adapter.stub!(:active?)
7
+ mock_connection_adapter.stub!(:disconnect!)
8
+ mock_connection_adapter
9
+ end
10
+
11
+ describe ConnectionPool do
12
+ describe "initialize ConnectionPool" do
13
+ it "raise ArgumentError if block not given" do
14
+ lambda{ConnectionPool.new}.should raise_error(ArgumentError)
15
+ end
16
+ it "connection pool size default 5, and can customize" do
17
+ ConnectionPool.new{}.options[:size].should == 5
18
+ ConnectionPool.new(:size => 10){}.options[:size].should == 10
19
+ end
20
+ it "connection timeout default 5, and can customize" do
21
+ ConnectionPool.new{}.options[:timeout].should == 5
22
+ ConnectionPool.new(:timeout => 10){}.options[:timeout].should == 10
23
+ end
24
+ end
25
+
26
+ describe "#connection" do
27
+ before do
28
+ @connection_pool = ConnectionPool.new{mock_connection_adapter}
29
+ end
30
+ it "should return a connection from connection pool" do
31
+ @connection_pool.connection.should_not be_nil
32
+ end
33
+ it "should cache the connection when call the #connection method and repeat call multi times should return the same connection if in the same thread" do
34
+ @connection_pool.connection.should == @connection_pool.connection
35
+ @connection_pool.should be_connection_cached
36
+ end
37
+ it "in different threads should return different connection when call #connection method" do
38
+ main_thread_connection = @connection_pool.connection
39
+ Thread.new{@connection_pool.connection.should_not == main_thread_connection}
40
+ end
41
+ end
42
+
43
+ describe "#release_connection" do
44
+ before do
45
+ @connection_pool = ConnectionPool.new{mock_connection_adapter}
46
+ end
47
+ it "should delete the cached connection" do
48
+ @connection_pool.connection
49
+ @connection_pool.release_connection
50
+ @connection_pool.should_not be_connection_cached
51
+ end
52
+ it "should check in the connection back to the connection pool" do
53
+ @connection_pool.connection
54
+ @connection_pool.release_connection
55
+ @connection_pool.instance_variable_get("@checked_out").should be_empty
56
+ end
57
+ end
58
+
59
+ describe "#checkout " do
60
+ before do
61
+ @connection_pool = ConnectionPool.new{mock_connection_adapter}
62
+ @exist_connections = []
63
+ 5.times do
64
+ Thread.new{con = @connection_pool.checkout; @exist_connections << con}
65
+ end
66
+ @exist_connections.each{|con|@connection_pool.checkin(con)}
67
+ end
68
+ it "should checkout the exist connection if connections not empty" do
69
+ con = @connection_pool.checkout
70
+ @exist_connections.should include(con)
71
+ end
72
+ it "should raise error if can not obtain a connection for timeout" do
73
+ threads = []
74
+ 5.times do
75
+ threads << Thread.new{@connection_pool.checkout; sleep(5)}
76
+ end
77
+ lambda{@connection_pool.checkout}.should raise_error(ConnectionPool::ConnectionTimeoutError)
78
+ threads.each(&:join)
79
+ end
80
+ end
81
+
82
+ describe "#with_connection" do
83
+ before do
84
+ @connection_pool = ConnectionPool.new{mock_connection_adapter}
85
+ end
86
+ it "should check in the connection back to the pool if it is a fresh connection" do
87
+ @connection_pool.with_connection{|connection|}
88
+ @connection_pool.should_not be_connection_cached
89
+ @connection_pool.instance_variable_get("@checked_out").should be_empty
90
+ end
91
+ it "should not check in the connection back to the pool if it is a cached connection" do
92
+ @connection_pool.connection
93
+ @connection_pool.with_connection{|connection|}
94
+ @connection_pool.should be_connection_cached
95
+ @connection_pool.instance_variable_get("@checked_out").should_not be_empty
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,2 @@
1
+ $:.unshift(File.expand_path('../../lib', __FILE__))
2
+ require 'connection_pool'
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: generic_connection_pool
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - kame
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-28 00:00:00 +08:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activesupport
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ description: " Always network clients require a connection pool, like database connection, cache connection and others.\n\
50
+ Generic connection pool can be used with anything. It is inspired from ActiveRecord ConnectionPool.\n\
51
+ Sharing a limited number of network connections among many threads.\n\
52
+ Connections are created delayed.\n"
53
+ email:
54
+ - kamechb@gmail.com
55
+ executables: []
56
+
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - lib/connection_pool.rb
63
+ - lib/generic_connection_pool.rb
64
+ - spec/connection_pool_spec.rb
65
+ - spec/spec_helper.rb
66
+ - Rakefile
67
+ - README
68
+ - Gemfile
69
+ - LICENSE
70
+ has_rdoc: true
71
+ homepage:
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options: []
76
+
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ none: false
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ hash: 3
85
+ segments:
86
+ - 0
87
+ version: "0"
88
+ required_rubygems_version: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ hash: 3
94
+ segments:
95
+ - 0
96
+ version: "0"
97
+ requirements: []
98
+
99
+ rubyforge_project:
100
+ rubygems_version: 1.6.2
101
+ signing_key:
102
+ specification_version: 3
103
+ summary: Always network clients require a connection pool, like database connection, cache connection and others. Generic connection pool can be used with anything. It is inspired from ActiveRecord ConnectionPool. Sharing a limited number of network connections among many threads. Connections are created delayed.
104
+ test_files: []
105
+