pools 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Michael Rykov
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.
@@ -0,0 +1,16 @@
1
+ pools
2
+ -----
3
+
4
+ Provides connection pooling for multiple services that
5
+ use persistent connections
6
+
7
+ Installation
8
+ ============
9
+
10
+ $ gem install pools
11
+
12
+
13
+ Author
14
+ =====
15
+
16
+ Michael Rykov :: mrykov@gmail.com
@@ -0,0 +1,10 @@
1
+ require 'rspec/core'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => :spec
5
+ task :test => :spec
6
+
7
+ task :noop do; end
8
+
9
+ desc "Run all specs in spec directory (excluding plugin specs)"
10
+ RSpec::Core::RakeTask.new(:spec => :noop)
@@ -0,0 +1,4 @@
1
+ require 'pools/connection_pool'
2
+ require 'pools/handler'
3
+ require 'pools/middleware'
4
+ require 'pools/pooled'
@@ -0,0 +1,236 @@
1
+ ##
2
+ # This file adapted from activerecord gem
3
+ #
4
+
5
+ require 'thread'
6
+ require 'monitor'
7
+ require 'set'
8
+ require 'active_support/core_ext/module/synchronization'
9
+
10
+ module Pools
11
+ # Raised when a connection could not be obtained within the connection
12
+ # acquisition timeout period.
13
+ ConnectionNotEstablished = Class.new(StandardError)
14
+ ConnectionTimeoutError = Class.new(ConnectionNotEstablished)
15
+
16
+ # Connection pool base class for managing Active Record database
17
+ # connections.
18
+ #
19
+ # == Introduction
20
+ #
21
+ # A connection pool synchronizes thread access to a limited number of
22
+ # database connections. The basic idea is that each thread checks out a
23
+ # database connection from the pool, uses that connection, and checks the
24
+ # connection back in. ConnectionPool is completely thread-safe, and will
25
+ # ensure that a connection cannot be used by two threads at the same time,
26
+ # as long as ConnectionPool's contract is correctly followed. It will also
27
+ # handle cases in which there are more threads than connections: if all
28
+ # connections have been checked out, and a thread tries to checkout a
29
+ # connection anyway, then ConnectionPool will wait until some other thread
30
+ # has checked in a connection.
31
+ #
32
+ # == Obtaining (checking out) a connection
33
+ #
34
+ # Connections can be obtained and used from a connection pool in several
35
+ # ways:
36
+ #
37
+ # 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
38
+ # earlier (pre-connection-pooling). Eventually, when you're done with
39
+ # the connection(s) and wish it to be returned to the pool, you call
40
+ # ActiveRecord::Base.clear_active_connections!. This will be the
41
+ # default behavior for Active Record when used in conjunction with
42
+ # Action Pack's request handling cycle.
43
+ # 2. Manually check out a connection from the pool with
44
+ # ActiveRecord::Base.connection_pool.checkout. You are responsible for
45
+ # returning this connection to the pool when finished by calling
46
+ # ActiveRecord::Base.connection_pool.checkin(connection).
47
+ # 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
48
+ # obtains a connection, yields it as the sole argument to the block,
49
+ # and returns it to the pool after the block completes.
50
+ #
51
+ # Connections in the pool are actually AbstractAdapter objects (or objects
52
+ # compatible with AbstractAdapter's interface).
53
+ #
54
+ # == Options
55
+ #
56
+ # There are two connection-pooling-related options that you can add to
57
+ # your database connection configuration:
58
+ #
59
+ # * +pool+: number indicating size of connection pool (default 5)
60
+ # * +wait_timeout+: number of seconds to block and wait for a connection
61
+ # before giving up and raising a timeout error (default 5 seconds).
62
+ class ConnectionPool
63
+ attr_reader :options, :connections
64
+
65
+ # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
66
+ # object which describes database connection information (e.g. adapter,
67
+ # host name, username, password, etc), as well as the maximum size for
68
+ # this ConnectionPool.
69
+ #
70
+ # The default ConnectionPool maximum size is 5.
71
+ def initialize(pooled, options)
72
+ @pooled = pooled
73
+ @options = options
74
+
75
+ # The cache of reserved connections mapped to threads
76
+ @reserved_connections = {}
77
+
78
+ # The mutex used to synchronize pool access
79
+ @connection_mutex = Monitor.new
80
+ @queue = @connection_mutex.new_cond
81
+
82
+ # default 5 second timeout unless on ruby 1.9
83
+ @timeout = options[:wait_timeout] || 5
84
+
85
+ # default max pool size to 5
86
+ @size = (options[:pool] && options[:pool].to_i) || 5
87
+
88
+ @connections = []
89
+ @checked_out = []
90
+ end
91
+
92
+ # Retrieve the connection associated with the current thread, or call
93
+ # #checkout to obtain one if necessary.
94
+ #
95
+ # #connection can be called any number of times; the connection is
96
+ # held in a hash keyed by the thread id.
97
+ def connection
98
+ @reserved_connections[current_connection_id] ||= checkout
99
+ end
100
+
101
+ # Signal that the thread is finished with the current connection.
102
+ # #release_connection releases the connection-thread association
103
+ # and returns the connection to the pool.
104
+ def release_connection(with_id = current_connection_id)
105
+ conn = @reserved_connections.delete(with_id)
106
+ checkin conn if conn
107
+ end
108
+
109
+ # If a connection already exists yield it to the block. If no connection
110
+ # exists checkout a connection, yield it to the block, and checkin the
111
+ # connection when finished.
112
+ def with_connection
113
+ connection_id = current_connection_id
114
+ fresh_connection = true unless @reserved_connections[connection_id]
115
+ yield connection
116
+ ensure
117
+ release_connection(connection_id) if fresh_connection
118
+ end
119
+
120
+ # Returns true if a connection has already been opened.
121
+ def connected?
122
+ !@connections.empty?
123
+ end
124
+
125
+ # Disconnects all connections in the pool, and clears the pool.
126
+ def disconnect!
127
+ @reserved_connections.each do |name,conn|
128
+ checkin conn
129
+ end
130
+ @reserved_connections = {}
131
+ @connections.each do |conn|
132
+ @pooled.__disconnect(conn)
133
+ end
134
+ @connections = []
135
+ end
136
+
137
+ # Verify active connections and remove and disconnect connections
138
+ # associated with stale threads.
139
+ def verify_active_connections! #:nodoc:
140
+ clear_stale_cached_connections!
141
+ @connections.each do |connection|
142
+ @pooled.__disconnect(connection)
143
+ end
144
+ end
145
+
146
+ # Return any checked-out connections back to the pool by threads that
147
+ # are no longer alive.
148
+ def clear_stale_cached_connections!
149
+ keys = @reserved_connections.keys - Thread.list.find_all { |t|
150
+ t.alive?
151
+ }.map { |thread| thread.object_id }
152
+ keys.each do |key|
153
+ checkin @reserved_connections[key]
154
+ @reserved_connections.delete(key)
155
+ end
156
+ end
157
+
158
+ # Check-out a database connection from the pool, indicating that you want
159
+ # to use it. You should call #checkin when you no longer need this.
160
+ #
161
+ # This is done by either returning an existing connection, or by creating
162
+ # a new connection. If the maximum number of connections for this pool has
163
+ # already been reached, but the pool is empty (i.e. they're all being used),
164
+ # then this method will wait until a thread has checked in a connection.
165
+ # The wait time is bounded however: if no connection can be checked out
166
+ # within the timeout specified for this pool, then a ConnectionTimeoutError
167
+ # exception will be raised.
168
+ #
169
+ # Returns: an AbstractAdapter object.
170
+ #
171
+ # Raises:
172
+ # - ConnectionTimeoutError: no connection can be obtained from the pool
173
+ # within the timeout period.
174
+ def checkout
175
+ # Checkout an available connection
176
+ @connection_mutex.synchronize do
177
+ loop do
178
+ conn = if @checked_out.size < @connections.size
179
+ checkout_existing_connection
180
+ elsif @connections.size < @size
181
+ checkout_new_connection
182
+ end
183
+ return conn if conn
184
+
185
+ @queue.wait(@timeout)
186
+
187
+ if(@checked_out.size < @connections.size)
188
+ next
189
+ else
190
+ clear_stale_cached_connections!
191
+ if @size == @checked_out.size
192
+ raise ConnectionTimeoutError, "could not obtain a pooled connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it."
193
+ end
194
+ end
195
+
196
+ end
197
+ end
198
+ end
199
+
200
+ # Check-in a database connection back into the pool, indicating that you
201
+ # no longer need this connection.
202
+ #
203
+ # +conn+: an AbstractAdapter object, which was obtained by earlier by
204
+ # calling +checkout+ on this pool.
205
+ def checkin(conn)
206
+ @connection_mutex.synchronize do
207
+ @checked_out.delete conn
208
+ @queue.signal
209
+ end
210
+ end
211
+
212
+ synchronize :verify_active_connections!, :connected?, :disconnect!,
213
+ :with => :@connection_mutex
214
+
215
+ private
216
+ def current_connection_id #:nodoc:
217
+ Thread.current.object_id
218
+ end
219
+
220
+ def checkout_new_connection
221
+ c = @pooled.__connection
222
+ @connections << c
223
+ checkout_connection(c)
224
+ end
225
+
226
+ def checkout_existing_connection
227
+ c = (@connections - @checked_out).first
228
+ checkout_connection(c)
229
+ end
230
+
231
+ def checkout_connection(c)
232
+ @checked_out << c
233
+ c
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,62 @@
1
+ ##
2
+ # This file adapted from activerecord gem
3
+ #
4
+
5
+ module Pools
6
+ class Handler
7
+ attr_reader :pools
8
+
9
+ def initialize(pools = {})
10
+ @pools = pools
11
+ end
12
+
13
+ # Add a new connection pool to the mix
14
+ def add(pool, name = nil)
15
+ @pools[name || pool.object_id] = pool
16
+ end
17
+
18
+ # Returns any connections in use by the current thread back to the
19
+ # pool, and also returns connections to the pool cached by threads
20
+ # that are no longer alive.
21
+ def clear_active_connections!
22
+ @pools.each_value {|pool| pool.release_connection }
23
+ end
24
+
25
+ def clear_all_connections!
26
+ @pools.each_value {|pool| pool.disconnect! }
27
+ end
28
+
29
+ # Verify active connections.
30
+ def verify_active_connections! #:nodoc:
31
+ @pools.each_value {|pool| pool.verify_active_connections! }
32
+ end
33
+
34
+ # Returns true if a connection that's accessible to this class has
35
+ # already been opened.
36
+ def connected?(name)
37
+ conn = retrieve_connection_pool(name)
38
+ conn && conn.connected?
39
+ end
40
+
41
+ # Remove the connection for this class. This will close the active
42
+ # connection and the defined connection (if they exist). The result
43
+ # can be used as an argument for establish_connection, for easily
44
+ # re-establishing the connection.
45
+ def remove_connection(name)
46
+ pool = retrieve_connection_pool(name)
47
+ return nil unless pool
48
+
49
+ @pools.delete_if { |key, value| value == pool }
50
+ pool.disconnect!
51
+ end
52
+
53
+ def retrieve_connection_pool(name)
54
+ pool = @pools[name]
55
+ return pool if pool
56
+ end
57
+ end
58
+
59
+ def self.handler
60
+ @@pool_handler ||= Handler.new
61
+ end
62
+ end
@@ -0,0 +1,17 @@
1
+ ##
2
+ # This file adapted from activerecord gem
3
+ #
4
+
5
+ module Pools
6
+ class Middleware
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ @app.call(env)
13
+ ensure
14
+ Pools.handler.clear_active_connections!
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ require 'active_support/core_ext/array/extract_options'
2
+ require 'active_support/concern'
3
+
4
+ module Pools
5
+ module Pooled
6
+ extend ActiveSupport::Concern
7
+ attr_reader :connection_pool
8
+
9
+ def initialize(*args)
10
+ options = args.extract_options!
11
+ @connection_pool = ConnectionPool.new(self, options)
12
+ Pools.handler.add(@connection_pool, options[:pool_name])
13
+ end
14
+
15
+ def with_connection(&block)
16
+ @connection_pool.with_connection(&block)
17
+ end
18
+
19
+ def __connection
20
+ # Override in parent
21
+ end
22
+
23
+ def __disconnect(connection)
24
+ # Override in parent
25
+ end
26
+
27
+ module ClassMethods
28
+ def connection_methods(*methods)
29
+ methods.each do |method|
30
+ define_method(method) do |*params|
31
+ with_connection do |client|
32
+ client.send(method, *params)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,43 @@
1
+ require 'redis'
2
+
3
+ class Redis
4
+ class Pooled
5
+ include ::Pools::Pooled
6
+
7
+ def initialize(options = {})
8
+ @redis_options = options
9
+ super
10
+ end
11
+
12
+ def __connection
13
+ Redis.connect(@redis_options)
14
+ end
15
+
16
+ def __disconnect(client)
17
+ client.quit if client
18
+ end
19
+
20
+ # Method not supported:
21
+ # Subscribe/Unsubscribe methods and the following...
22
+ # :auth, :select, :discard, :quit, :watch, :unwatch
23
+ # :exec, :multi, :disconnect
24
+
25
+ connection_methods :info, :config, :flushdb, :flushall, :save,
26
+ :bgsave, :bgrewriteaof, :get, :getset, :mget, :append, :substr,
27
+ :strlen, :hgetall, :hget, :hdel, :hkeys, :keys, :randomkey,
28
+ :echo, :ping, :lastsave, :dbsize, :exists, :llen, :lrange,
29
+ :ltrim, :lindex, :linsert, :lset, :lrem, :rpush, :rpushx,
30
+ :lpush, :lpushx, :rpop, :blpop, :brpop, :rpoplpush, :lpop,
31
+ :smembers, :sismember, :sadd, :srem, :smove, :sdiff, :sdiffstore,
32
+ :sinter, :sinterstore, :sunion, :sunionstore, :spop, :scard,
33
+ :srandmember, :zadd, :zrank, :zrevrank, :zincrby, :zcard,
34
+ :zrange, :zrangebyscore, :zcount, :zrevrange, :zremrangebyscore,
35
+ :zremrangebyrank, :zscore, :zrem, :zinterstore, :zunionstore,
36
+ :move, :setnx, :del, :rename, :renamenx, :expire, :persist,
37
+ :ttl, :expireat, :hset, :hsetnx, :hmset, :mapped_hmset, :hmget,
38
+ :mapped_hmget, :hlen, :hvals, :hincrby, :hexists, :monitor,
39
+ :debug, :sync, :[], :[]=, :set, :setex, :mset, :mapped_mset,
40
+ :msetnx, :mapped_msetnx, :mapped_mget, :sort, :incr, :incrby,
41
+ :decr, :decrby, :type, :publish, :id
42
+ end
43
+ end
@@ -0,0 +1,40 @@
1
+ require 'redis-store'
2
+ require 'redis/pooled'
3
+
4
+ class Redis
5
+ class PooledStore < Pooled
6
+ include Store::Ttl, Store::Interface
7
+
8
+ def initialize(options = { })
9
+ super
10
+ _extend_marshalling options
11
+ end
12
+
13
+ def self.rails3? #:nodoc:
14
+ defined?(::Rails) && ::Rails::VERSION::MAJOR == 3
15
+ end
16
+
17
+ def to_s
18
+ with_connection do |c|
19
+ "Redis::Pooled => #{c.host}:#{c.port} against DB #{c.db}"
20
+ end
21
+ end
22
+
23
+ private
24
+ def _extend_marshalling(options) # Copied from Store
25
+ @marshalling = !(options[:marshalling] === false)
26
+ extend Store::Marshalling if @marshalling
27
+ end
28
+ end
29
+
30
+ class << Store
31
+ def new(*args)
32
+ if args.size == 1 && args.first.is_a?(Hash) && args.first[:pool]
33
+ PooledStore.new(*args)
34
+ else
35
+ super(*args)
36
+ end
37
+ end
38
+ end
39
+ end
40
+
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pools
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.3
6
+ platform: ruby
7
+ authors:
8
+ - Michael Rykov
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-13 00:00:00 -08:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activesupport
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 3.0.5
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ description: |
28
+ Generalized connection pooling
29
+
30
+ email: mrykov@gmail
31
+ executables: []
32
+
33
+ extensions: []
34
+
35
+ extra_rdoc_files: []
36
+
37
+ files:
38
+ - README.md
39
+ - Rakefile
40
+ - LICENSE
41
+ - lib/pools.rb
42
+ - lib/pools/connection_pool.rb
43
+ - lib/pools/handler.rb
44
+ - lib/pools/middleware.rb
45
+ - lib/pools/pooled.rb
46
+ - lib/redis/pooled.rb
47
+ - lib/redis/pooled_store.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/rykov/pools
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.5.1
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Generalized connection pooling
76
+ test_files: []
77
+