pools 0.0.3

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/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
+