healthy_pools 2.2.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7920f22d2bf5c1bb58d00ac099f15d2c074587e8
4
+ data.tar.gz: b090b648aa7186b23387ac584d4851bf7ee3d947
5
+ SHA512:
6
+ metadata.gz: 27e4679398276cc5f2fe3086a61cdaf487f7c90b3d1a3b1432ae5c3f88587f8eaa5164613c5880ea6a390d5c55fe11917d6024b2f13cd4a83e522cfe8f0b4819
7
+ data.tar.gz: df8a0a87bed874e383c1516218e0e1678610e2d87e805952bb7f4a1222a498cd9d75e9b113fc2d1286de22debf5732573902fa5e48b9df78d0cc91f23776c34c
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,14 @@
1
+ ---
2
+ sudo: false
3
+ cache: bundler
4
+ language: ruby
5
+ rvm:
6
+ - 2.0.0
7
+ - 2.1.6
8
+ - 2.2.2
9
+ - 2.3.1
10
+ - 2.4.1
11
+ - jruby
12
+ notifications:
13
+ email:
14
+ - marcojoemontagna@gmail.com
data/Changes.md ADDED
@@ -0,0 +1,127 @@
1
+ healthy\_pools changelog
2
+ ---------------------------
3
+
4
+ HEAD
5
+ ------
6
+
7
+ 2.2.3
8
+ ------
9
+ - Fork connection_pool to new name healthy_pools.
10
+ - Add support for checking connection health.
11
+ - Add pool `size` and `available` accessors for metrics and monitoring
12
+ purposes [#97, robholland]
13
+
14
+ 2.2.1
15
+ ------
16
+
17
+ - Allow CP::Wrapper to use an existing pool [#87, etiennebarrie]
18
+ - Use monotonic time for more accurate timeouts [#84, jdantonio]
19
+
20
+ 2.2.0
21
+ ------
22
+
23
+ - Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems
24
+ impossible to safely work around the issue. Please never, ever use
25
+ `Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75]
26
+
27
+ 2.1.3
28
+ ------
29
+
30
+ - Don't increment created count until connection is successfully
31
+ created. [mylesmegyesi, #73]
32
+
33
+ 2.1.2
34
+ ------
35
+
36
+ - The connection\_pool will now close any connections which respond to
37
+ `close` (Dalli) or `disconnect!` (Redis). This ensures discarded connections
38
+ from the fix in 2.1.1 are torn down ASAP and don't linger open.
39
+
40
+
41
+ 2.1.1
42
+ ------
43
+
44
+ - Work around a subtle race condition with code which uses `Timeout.timeout` and
45
+ checks out a connection within the timeout block. This might cause
46
+ connections to get into a bad state and raise very odd errors. [tamird, #67]
47
+
48
+
49
+ 2.1.0
50
+ ------
51
+
52
+ - Refactoring to better support connection pool subclasses [drbrain,
53
+ #55]
54
+ - `with` should return value of the last expression [#59]
55
+
56
+
57
+ 2.0.0
58
+ -----
59
+
60
+ - The connection pool is now lazy. Connections are created as needed
61
+ and retained until the pool is shut down. [drbrain, #52]
62
+
63
+ 1.2.0
64
+ -----
65
+
66
+ - Add `with(options)` and `checkout(options)`. [mattcamuto]
67
+ Allows the caller to override the pool timeout.
68
+ ```ruby
69
+ @pool.with(:timeout => 2) do |conn|
70
+ end
71
+ ```
72
+
73
+ 1.1.0
74
+ -----
75
+
76
+ - New `#shutdown` method (simao)
77
+
78
+ This method accepts a block and calls the block for each
79
+ connection in the pool. After calling this method, trying to get a
80
+ connection from the pool raises `PoolShuttingDownError`.
81
+
82
+ 1.0.0
83
+ -----
84
+
85
+ - `#with_connection` is now gone in favor of `#with`.
86
+
87
+ - We no longer pollute the top level namespace with our internal
88
+ `TimedStack` class.
89
+
90
+ 0.9.3
91
+ --------
92
+
93
+ - `#with_connection` is now deprecated in favor of `#with`.
94
+
95
+ A warning will be issued in the 0.9 series and the method will be
96
+ removed in 1.0.
97
+
98
+ - We now reuse objects when possible.
99
+
100
+ This means that under no contention, the same object will be checked
101
+ out from the pool after subsequent calls to `ConnectionPool#with`.
102
+
103
+ This change should have no impact on end user performance. If
104
+ anything, it should be an improvement, depending on what objects you
105
+ are pooling.
106
+
107
+ 0.9.2
108
+ --------
109
+
110
+ - Fix reentrant checkout leading to early checkin.
111
+
112
+ 0.9.1
113
+ --------
114
+
115
+ - Fix invalid superclass in version.rb
116
+
117
+ 0.9.0
118
+ --------
119
+
120
+ - Move method\_missing magic into ConnectionPool::Wrapper (djanowski)
121
+ - Remove BasicObject superclass (djanowski)
122
+
123
+ 0.1.0
124
+ --------
125
+
126
+ - More precise timeouts and better error message
127
+ - ConnectionPool now subclasses BasicObject so `method_missing` is more effective.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec(development_group: :runtime)
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Mike Perham
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.md ADDED
@@ -0,0 +1,114 @@
1
+ connection\_pool
2
+ =================
3
+ [![Build Status](https://travis-ci.org/marcomontagna/healthy_pools.svg)](https://travis-ci.org/marcomontagna/healthy_pools)
4
+
5
+ Generic connection pooling for Ruby.
6
+
7
+ MongoDB has its own connection pool. ActiveRecord has its own connection pool.
8
+ This is a generic connection pool that can be used with anything, e.g. Redis,
9
+ Dalli and other Ruby network clients.
10
+
11
+ This is a fork of [connection_pool](https://github.com/mperham/connection_pool) with
12
+ support for checking connection health.
13
+
14
+
15
+ Usage
16
+ -----
17
+
18
+ Create a pool of objects to share amongst the fibers or threads in your Ruby
19
+ application:
20
+
21
+ ``` ruby
22
+ $memcached = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new }
23
+ ```
24
+
25
+ Then use the pool in your application:
26
+
27
+ ``` ruby
28
+ $memcached.with do |conn|
29
+ conn.get('some-count')
30
+ end
31
+ ```
32
+
33
+ If all the objects in the connection pool are in use, `with` will block
34
+ until one becomes available. If no object is available within `:timeout` seconds,
35
+ `with` will raise a `Timeout::Error`.
36
+
37
+ Optionally, you can specify a timeout override using the with-block semantics:
38
+
39
+ ``` ruby
40
+ $memcached.with(timeout: 2.0) do |conn|
41
+ conn.get('some-count')
42
+ end
43
+ ```
44
+
45
+ This will only modify the resource-get timeout for this particular
46
+ invocation. This is useful if you want to fail-fast on certain non critical
47
+ sections when a resource is not available, or conversely if you are comfortable
48
+ blocking longer on a particular resource. This is not implemented in the below
49
+ `ConnectionPool::Wrapper` class.
50
+
51
+ ## Migrating to a Connection Pool
52
+
53
+ You can use `ConnectionPool::Wrapper` to wrap a single global connection,
54
+ making it easier to migrate existing connection code over time:
55
+
56
+ ``` ruby
57
+ $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect }
58
+ $redis.sadd('foo', 1)
59
+ $redis.smembers('foo')
60
+ ```
61
+
62
+ The wrapper uses `method_missing` to checkout a connection, run the requested
63
+ method and then immediately check the connection back into the pool. It's
64
+ **not** high-performance so you'll want to port your performance sensitive code
65
+ to use `with` as soon as possible.
66
+
67
+ ``` ruby
68
+ $redis.with do |conn|
69
+ conn.sadd('foo', 1)
70
+ conn.smembers('foo')
71
+ end
72
+ ```
73
+
74
+ Once you've ported your entire system to use `with`, you can simply remove
75
+ `Wrapper` and use the simpler and faster `ConnectionPool`.
76
+
77
+
78
+ ## Shutdown
79
+
80
+ You can shut down a ConnectionPool instance once it should no longer be used.
81
+ Further checkout attempts will immediately raise an error but existing checkouts
82
+ will work.
83
+
84
+ ```ruby
85
+ cp = ConnectionPool.new { Redis.new }
86
+ cp.shutdown { |conn| conn.quit }
87
+ ```
88
+
89
+ Shutting down a connection pool will block until all connections are checked in and closed.
90
+ **Note that shutting down is completely optional**; Ruby's garbage collector will reclaim
91
+ unreferenced pools under normal circumstances.
92
+
93
+ ## Managing Health of Connections
94
+
95
+ You can pass a health check lambda or Proc to the connection pool which is called
96
+ before a connection is returned to clients to verify the connection is still good.
97
+ If the health check returns false or throws an exception the connection is removed
98
+ from the pool and a new one is created.
99
+
100
+
101
+ ```ruby
102
+ cp = ConnectionPool.new(health_check: lambda {|conn| conn.exec('select 1')}) { PG.connect }
103
+ ```
104
+
105
+
106
+ Notes
107
+ -----
108
+
109
+ - Connections are lazily created as needed.
110
+ - **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
111
+ occasional silent corruption and mysterious errors. The Timeout API is unsafe
112
+ and cannot be used correctly, ever. Use proper socket timeout options as
113
+ exposed by Net::HTTP, Redis, Dalli, etc.
114
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new
5
+
6
+ task :default => :test
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require "./lib/connection_pool/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "healthy_pools"
6
+ s.version = ConnectionPool::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Marco Montagna"]
9
+ s.email = ["marcojoemontagna@gmail.com"]
10
+ s.homepage = "https://github.com/mmontagna/healthy_pools"
11
+ s.description = s.summary = %q{Generic connection pool for Ruby}
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+ s.license = "MIT"
18
+ s.add_development_dependency 'bundler'
19
+ s.add_development_dependency 'minitest', '>= 5.0.0'
20
+ s.add_development_dependency 'rake'
21
+ end
@@ -0,0 +1,66 @@
1
+ # Global monotonic clock from Concurrent Ruby 1.0.
2
+ # Copyright (c) Jerry D'Antonio -- released under the MIT license.
3
+ # Slightly modified; used with permission.
4
+ # https://github.com/ruby-concurrency/concurrent-ruby
5
+
6
+ require 'thread'
7
+
8
+ class ConnectionPool
9
+
10
+ class_definition = Class.new do
11
+
12
+ if defined?(Process::CLOCK_MONOTONIC)
13
+
14
+ # @!visibility private
15
+ def get_time
16
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
17
+ end
18
+
19
+ elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
20
+
21
+ # @!visibility private
22
+ def get_time
23
+ java.lang.System.nanoTime() / 1_000_000_000.0
24
+ end
25
+
26
+ else
27
+
28
+ # @!visibility private
29
+ def initialize
30
+ @mutex = Mutex.new
31
+ @last_time = Time.now.to_f
32
+ end
33
+
34
+ # @!visibility private
35
+ def get_time
36
+ @mutex.synchronize do
37
+ now = Time.now.to_f
38
+ if @last_time < now
39
+ @last_time = now
40
+ else # clock has moved back in time
41
+ @last_time += 0.000_001
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ ##
49
+ # Clock that cannot be set and represents monotonic time since
50
+ # some unspecified starting point.
51
+ #
52
+ # @!visibility private
53
+ GLOBAL_MONOTONIC_CLOCK = class_definition.new
54
+ private_constant :GLOBAL_MONOTONIC_CLOCK
55
+
56
+ class << self
57
+ ##
58
+ # Returns the current time a tracked by the application monotonic clock.
59
+ #
60
+ # @return [Float] The current monotonic time when `since` not given else
61
+ # the elapsed monotonic time between `since` and the current time
62
+ def monotonic_time
63
+ GLOBAL_MONOTONIC_CLOCK.get_time
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,201 @@
1
+ require 'thread'
2
+ require 'timeout'
3
+ require_relative 'monotonic_time'
4
+
5
+ ##
6
+ # Raised when you attempt to retrieve a connection from a pool that has been
7
+ # shut down.
8
+
9
+ class ConnectionPool::PoolShuttingDownError < RuntimeError; end
10
+
11
+ ##
12
+ # The TimedStack manages a pool of homogeneous connections (or any resource
13
+ # you wish to manage). Connections are created lazily up to a given maximum
14
+ # number.
15
+
16
+ # Examples:
17
+ #
18
+ # ts = TimedStack.new(1) { MyConnection.new }
19
+ #
20
+ # # fetch a connection
21
+ # conn = ts.pop
22
+ #
23
+ # # return a connection
24
+ # ts.push conn
25
+ #
26
+ # conn = ts.pop
27
+ # ts.pop timeout: 5
28
+ # #=> raises Timeout::Error after 5 seconds
29
+
30
+ class ConnectionPool::TimedStack
31
+ attr_reader :max
32
+
33
+ ##
34
+ # Creates a new pool with +size+ connections that are created from the given
35
+ # +block+.
36
+
37
+ def initialize(size = 0, health_check: nil, &block)
38
+ @create_block = block
39
+ @health_check = health_check
40
+ @created = 0
41
+ @que = []
42
+ @max = size
43
+ @mutex = Mutex.new
44
+ @resource = ConditionVariable.new
45
+ @shutdown_block = nil
46
+ end
47
+
48
+ ##
49
+ # Removes +conn+ from the stack. This is useful if connections
50
+ # die or otherwise become stale.
51
+
52
+ def delete(conn)
53
+ if @mutex.owned?
54
+ @created -= 1
55
+ else
56
+ @mutex.synchronize do
57
+ @created -= 1
58
+ end
59
+ end
60
+ end
61
+
62
+ ##
63
+ # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be
64
+ # used by subclasses that extend TimedStack.
65
+
66
+ def push(obj, options = {})
67
+ @mutex.synchronize do
68
+ if @shutdown_block
69
+ @shutdown_block.call(obj)
70
+ else
71
+ store_connection obj, options
72
+ end
73
+
74
+ @resource.broadcast
75
+ end
76
+ end
77
+ alias_method :<<, :push
78
+
79
+ ##
80
+ # Retrieves a connection from the stack. If a connection is available it is
81
+ # immediately returned. If no connection is available within the given
82
+ # timeout a Timeout::Error is raised.
83
+ #
84
+ # +:timeout+ is the only checked entry in +options+ and is preferred over
85
+ # the +timeout+ argument (which will be removed in a future release). Other
86
+ # options may be used by subclasses that extend TimedStack.
87
+
88
+ def pop(timeout = 0.5, options = {})
89
+ options, timeout = timeout, 0.5 if Hash === timeout
90
+ timeout = options.fetch :timeout, timeout
91
+
92
+ deadline = ConnectionPool.monotonic_time + timeout
93
+ @mutex.synchronize do
94
+ loop do
95
+ raise ConnectionPool::PoolShuttingDownError if @shutdown_block
96
+ while connection_stored?(options)
97
+ conn = fetch_connection(options)
98
+ begin
99
+ if @health_check.nil? || @health_check.call(conn)
100
+ return conn
101
+ end
102
+ rescue
103
+ end
104
+ # Health check has failed, delete conn and retry.
105
+ delete(conn)
106
+ end
107
+
108
+ connection = try_create(options)
109
+ return connection if connection
110
+
111
+ to_wait = deadline - ConnectionPool.monotonic_time
112
+ raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0
113
+ @resource.wait(@mutex, to_wait)
114
+ end
115
+ end
116
+ end
117
+
118
+ ##
119
+ # Shuts down the TimedStack which prevents connections from being checked
120
+ # out. The +block+ is called once for each connection on the stack.
121
+
122
+ def shutdown(&block)
123
+ raise ArgumentError, "shutdown must receive a block" unless block_given?
124
+
125
+ @mutex.synchronize do
126
+ @shutdown_block = block
127
+ @resource.broadcast
128
+
129
+ shutdown_connections
130
+ end
131
+ end
132
+
133
+ ##
134
+ # Returns +true+ if there are no available connections.
135
+
136
+ def empty?
137
+ (@created - @que.length) >= @max
138
+ end
139
+
140
+ ##
141
+ # The number of connections available on the stack.
142
+
143
+ def length
144
+ @max - @created + @que.length
145
+ end
146
+
147
+ private
148
+
149
+ ##
150
+ # This is an extension point for TimedStack and is called with a mutex.
151
+ #
152
+ # This method must returns true if a connection is available on the stack.
153
+
154
+ def connection_stored?(options = nil)
155
+ !@que.empty?
156
+ end
157
+
158
+ ##
159
+ # This is an extension point for TimedStack and is called with a mutex.
160
+ #
161
+ # This method must return a connection from the stack.
162
+
163
+ def fetch_connection(options = nil)
164
+ @que.pop
165
+ end
166
+
167
+ ##
168
+ # This is an extension point for TimedStack and is called with a mutex.
169
+ #
170
+ # This method must shut down all connections on the stack.
171
+
172
+ def shutdown_connections(options = nil)
173
+ while connection_stored?(options)
174
+ conn = fetch_connection(options)
175
+ @shutdown_block.call(conn)
176
+ end
177
+ end
178
+
179
+ ##
180
+ # This is an extension point for TimedStack and is called with a mutex.
181
+ #
182
+ # This method must return +obj+ to the stack.
183
+
184
+ def store_connection(obj, options = nil)
185
+ @que.push obj
186
+ end
187
+
188
+ ##
189
+ # This is an extension point for TimedStack and is called with a mutex.
190
+ #
191
+ # This method must create a connection if and only if the total number of
192
+ # connections allowed has not been met.
193
+
194
+ def try_create(options = nil)
195
+ unless @created == @max
196
+ object = @create_block.call
197
+ @created += 1
198
+ object
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,3 @@
1
+ class ConnectionPool
2
+ VERSION = "2.2.3"
3
+ end