connection_pool 2.1.3 → 2.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -8
- data/Changes.md +22 -0
- data/README.md +12 -11
- data/lib/connection_pool.rb +58 -34
- data/lib/connection_pool/monotonic_time.rb +66 -0
- data/lib/connection_pool/timed_stack.rb +4 -24
- data/lib/connection_pool/version.rb +1 -1
- data/test/test_connection_pool.rb +109 -57
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf8177bf07f6c8e68b13f28416f6ccc0c64803af
|
4
|
+
data.tar.gz: ff9c836ce5576d4d0cb1edf8d858a761f982e437
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 73bda4d8d0b3cc9daef5c93bb98b7649d811bd5c8f467e25c3aaf7d3353541225e7b30e1efd481a14e45f5c56b5fc846d49dabdd9a167702a71c4a9631a9b6a2
|
7
|
+
data.tar.gz: a331275f67b3635e09792e646113e32bdc74254a2cdff032e972c6f2ebedd6391a1e508c36be7c4e33b40e2a07efe029a2ad0267c3ba4dc20b22813df86427bb
|
data/.travis.yml
CHANGED
data/Changes.md
CHANGED
@@ -1,3 +1,25 @@
|
|
1
|
+
connection\_pool changelog
|
2
|
+
---------------------------
|
3
|
+
|
4
|
+
2.2.2
|
5
|
+
------
|
6
|
+
|
7
|
+
- Add pool `size` and `available` accessors for metrics and monitoring
|
8
|
+
purposes [#97, robholland]
|
9
|
+
|
10
|
+
2.2.1
|
11
|
+
------
|
12
|
+
|
13
|
+
- Allow CP::Wrapper to use an existing pool [#87, etiennebarrie]
|
14
|
+
- Use monotonic time for more accurate timeouts [#84, jdantonio]
|
15
|
+
|
16
|
+
2.2.0
|
17
|
+
------
|
18
|
+
|
19
|
+
- Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems
|
20
|
+
impossible to safely work around the issue. Please never, ever use
|
21
|
+
`Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75]
|
22
|
+
|
1
23
|
2.1.3
|
2
24
|
------
|
3
25
|
|
data/README.md
CHANGED
@@ -45,8 +45,10 @@ sections when a resource is not available, or conversely if you are comfortable
|
|
45
45
|
blocking longer on a particular resource. This is not implemented in the below
|
46
46
|
`ConnectionPool::Wrapper` class.
|
47
47
|
|
48
|
+
## Migrating to a Connection Pool
|
49
|
+
|
48
50
|
You can use `ConnectionPool::Wrapper` to wrap a single global connection,
|
49
|
-
making it easier to
|
51
|
+
making it easier to migrate existing connection code over time:
|
50
52
|
|
51
53
|
``` ruby
|
52
54
|
$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect }
|
@@ -69,17 +71,20 @@ end
|
|
69
71
|
Once you've ported your entire system to use `with`, you can simply remove
|
70
72
|
`Wrapper` and use the simpler and faster `ConnectionPool`.
|
71
73
|
|
74
|
+
|
75
|
+
## Shutdown
|
76
|
+
|
72
77
|
You can shut down a ConnectionPool instance once it should no longer be used.
|
73
78
|
Further checkout attempts will immediately raise an error but existing checkouts
|
74
79
|
will work.
|
75
80
|
|
76
81
|
```ruby
|
77
82
|
cp = ConnectionPool.new { Redis.new }
|
78
|
-
cp.shutdown { |conn| conn.
|
83
|
+
cp.shutdown { |conn| conn.quit }
|
79
84
|
```
|
80
85
|
|
81
86
|
Shutting down a connection pool will block until all connections are checked in and closed.
|
82
|
-
Note that shutting down is completely optional
|
87
|
+
**Note that shutting down is completely optional**; Ruby's garbage collector will reclaim
|
83
88
|
unreferenced pools under normal circumstances.
|
84
89
|
|
85
90
|
|
@@ -90,14 +95,10 @@ Notes
|
|
90
95
|
- There is no provision for repairing or checking the health of a connection;
|
91
96
|
connections should be self-repairing. This is true of the Dalli and Redis
|
92
97
|
clients.
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
```
|
99
|
-
$ gem install connection_pool
|
100
|
-
```
|
98
|
+
- **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
|
99
|
+
occasional silent corruption and mysterious errors. The Timeout API is unsafe
|
100
|
+
and cannot be used correctly, ever. Use proper socket timeout options as
|
101
|
+
exposed by Net::HTTP, Redis, Dalli, etc.
|
101
102
|
|
102
103
|
|
103
104
|
Author
|
data/lib/connection_pool.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require_relative 'connection_pool/version'
|
2
2
|
require_relative 'connection_pool/timed_stack'
|
3
3
|
|
4
|
+
|
4
5
|
# Generic connection pool class for e.g. sharing a limited number of network connections
|
5
6
|
# among many threads. Note: Connections are lazily created.
|
6
7
|
#
|
@@ -14,7 +15,7 @@ require_relative 'connection_pool/timed_stack'
|
|
14
15
|
#
|
15
16
|
# Using optional timeout override (for that single invocation)
|
16
17
|
#
|
17
|
-
# @pool.with(:
|
18
|
+
# @pool.with(timeout: 2.0) do |redis|
|
18
19
|
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
19
20
|
# end
|
20
21
|
#
|
@@ -50,43 +51,60 @@ class ConnectionPool
|
|
50
51
|
|
51
52
|
@available = TimedStack.new(@size, &block)
|
52
53
|
@key = :"current-#{@available.object_id}"
|
54
|
+
@key_count = :"current-#{@available.object_id}-count"
|
55
|
+
end
|
56
|
+
|
57
|
+
if Thread.respond_to?(:handle_interrupt)
|
58
|
+
|
59
|
+
# MRI
|
60
|
+
def with(options = {})
|
61
|
+
Thread.handle_interrupt(Exception => :never) do
|
62
|
+
conn = checkout(options)
|
63
|
+
begin
|
64
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
65
|
+
yield conn
|
66
|
+
end
|
67
|
+
ensure
|
68
|
+
checkin
|
69
|
+
end
|
70
|
+
end
|
53
71
|
end
|
54
72
|
|
73
|
+
else
|
74
|
+
|
75
|
+
# jruby 1.7.x
|
55
76
|
def with(options = {})
|
56
|
-
# Connections can become corrupted via Timeout::Error. Discard
|
57
|
-
# any connection whose usage after checkout does not finish as expected.
|
58
|
-
# See #67
|
59
|
-
success = false
|
60
77
|
conn = checkout(options)
|
61
78
|
begin
|
62
|
-
|
63
|
-
success = true # means the connection wasn't interrupted
|
64
|
-
result
|
79
|
+
yield conn
|
65
80
|
ensure
|
66
|
-
|
67
|
-
# everything is roses, we can safely check the connection back in
|
68
|
-
checkin
|
69
|
-
else
|
70
|
-
@available.discard!(pop_connection)
|
71
|
-
end
|
81
|
+
checkin
|
72
82
|
end
|
73
83
|
end
|
74
84
|
|
85
|
+
end
|
86
|
+
|
75
87
|
def checkout(options = {})
|
76
|
-
|
77
|
-
|
78
|
-
@
|
88
|
+
if ::Thread.current[@key]
|
89
|
+
::Thread.current[@key_count]+= 1
|
90
|
+
::Thread.current[@key]
|
79
91
|
else
|
80
|
-
|
92
|
+
::Thread.current[@key_count]= 1
|
93
|
+
::Thread.current[@key]= @available.pop(options[:timeout] || @timeout)
|
81
94
|
end
|
82
|
-
|
83
|
-
stack.push conn
|
84
|
-
conn
|
85
95
|
end
|
86
96
|
|
87
97
|
def checkin
|
88
|
-
|
89
|
-
|
98
|
+
if ::Thread.current[@key]
|
99
|
+
if ::Thread.current[@key_count] == 1
|
100
|
+
@available.push(::Thread.current[@key])
|
101
|
+
::Thread.current[@key]= nil
|
102
|
+
else
|
103
|
+
::Thread.current[@key_count]-= 1
|
104
|
+
end
|
105
|
+
else
|
106
|
+
raise ConnectionPool::Error, 'no connections are checked out'
|
107
|
+
end
|
90
108
|
|
91
109
|
nil
|
92
110
|
end
|
@@ -95,25 +113,23 @@ class ConnectionPool
|
|
95
113
|
@available.shutdown(&block)
|
96
114
|
end
|
97
115
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
if stack.empty?
|
102
|
-
raise ConnectionPool::Error, 'no connections are checked out'
|
103
|
-
else
|
104
|
-
stack.pop
|
105
|
-
end
|
116
|
+
# Size of this connection pool
|
117
|
+
def size
|
118
|
+
@size
|
106
119
|
end
|
107
120
|
|
108
|
-
|
109
|
-
|
121
|
+
# Number of pool entries available for checkout at this instant.
|
122
|
+
def available
|
123
|
+
@available.length
|
110
124
|
end
|
111
125
|
|
126
|
+
private
|
127
|
+
|
112
128
|
class Wrapper < ::BasicObject
|
113
129
|
METHODS = [:with, :pool_shutdown]
|
114
130
|
|
115
131
|
def initialize(options = {}, &block)
|
116
|
-
@pool = ::ConnectionPool.new(options, &block)
|
132
|
+
@pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) }
|
117
133
|
end
|
118
134
|
|
119
135
|
def with(&block)
|
@@ -124,6 +140,14 @@ class ConnectionPool
|
|
124
140
|
@pool.shutdown(&block)
|
125
141
|
end
|
126
142
|
|
143
|
+
def pool_size
|
144
|
+
@pool.size
|
145
|
+
end
|
146
|
+
|
147
|
+
def pool_available
|
148
|
+
@pool.available
|
149
|
+
end
|
150
|
+
|
127
151
|
def respond_to?(id, *args)
|
128
152
|
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
129
153
|
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
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'thread'
|
2
2
|
require 'timeout'
|
3
|
+
require_relative 'monotonic_time'
|
3
4
|
|
4
5
|
##
|
5
6
|
# Raised when you attempt to retrieve a connection from a pool that has been
|
@@ -27,6 +28,7 @@ class ConnectionPool::PoolShuttingDownError < RuntimeError; end
|
|
27
28
|
# #=> raises Timeout::Error after 5 seconds
|
28
29
|
|
29
30
|
class ConnectionPool::TimedStack
|
31
|
+
attr_reader :max
|
30
32
|
|
31
33
|
##
|
32
34
|
# Creates a new pool with +size+ connections that are created from the given
|
@@ -72,7 +74,7 @@ class ConnectionPool::TimedStack
|
|
72
74
|
options, timeout = timeout, 0.5 if Hash === timeout
|
73
75
|
timeout = options.fetch :timeout, timeout
|
74
76
|
|
75
|
-
deadline =
|
77
|
+
deadline = ConnectionPool.monotonic_time + timeout
|
76
78
|
@mutex.synchronize do
|
77
79
|
loop do
|
78
80
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
@@ -81,7 +83,7 @@ class ConnectionPool::TimedStack
|
|
81
83
|
connection = try_create(options)
|
82
84
|
return connection if connection
|
83
85
|
|
84
|
-
to_wait = deadline -
|
86
|
+
to_wait = deadline - ConnectionPool.monotonic_time
|
85
87
|
raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0
|
86
88
|
@resource.wait(@mutex, to_wait)
|
87
89
|
end
|
@@ -117,28 +119,6 @@ class ConnectionPool::TimedStack
|
|
117
119
|
@max - @created + @que.length
|
118
120
|
end
|
119
121
|
|
120
|
-
##
|
121
|
-
# Indicates that a connection isn't coming back, allowing a new one to be
|
122
|
-
# created to replace it.
|
123
|
-
|
124
|
-
def discard!(obj)
|
125
|
-
@mutex.synchronize do
|
126
|
-
if @shutdown_block
|
127
|
-
@shutdown_block.call(obj)
|
128
|
-
else
|
129
|
-
# try to shut down the connection before throwing it away
|
130
|
-
if obj.respond_to?(:close) # Dalli::Client
|
131
|
-
obj.close rescue nil
|
132
|
-
elsif obj.respond_to?(:disconnect!) # Redis
|
133
|
-
obj.disconnect! rescue nil
|
134
|
-
end
|
135
|
-
@created -= 1
|
136
|
-
end
|
137
|
-
|
138
|
-
@resource.broadcast
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
122
|
private
|
143
123
|
|
144
124
|
##
|
@@ -3,13 +3,15 @@ require_relative 'helper'
|
|
3
3
|
class TestConnectionPool < Minitest::Test
|
4
4
|
|
5
5
|
class NetworkConnection
|
6
|
+
SLEEP_TIME = 0.1
|
7
|
+
|
6
8
|
def initialize
|
7
9
|
@x = 0
|
8
10
|
end
|
9
11
|
|
10
12
|
def do_something
|
11
13
|
@x += 1
|
12
|
-
sleep
|
14
|
+
sleep SLEEP_TIME
|
13
15
|
@x
|
14
16
|
end
|
15
17
|
|
@@ -19,7 +21,7 @@ class TestConnectionPool < Minitest::Test
|
|
19
21
|
|
20
22
|
def do_something_with_block
|
21
23
|
@x += yield
|
22
|
-
sleep
|
24
|
+
sleep SLEEP_TIME
|
23
25
|
@x
|
24
26
|
end
|
25
27
|
|
@@ -58,25 +60,30 @@ class TestConnectionPool < Minitest::Test
|
|
58
60
|
end
|
59
61
|
|
60
62
|
def test_basic_multithreaded_usage
|
61
|
-
|
63
|
+
pool_size = 5
|
64
|
+
pool = ConnectionPool.new(size: pool_size) { NetworkConnection.new }
|
65
|
+
|
66
|
+
start = Time.new
|
62
67
|
|
63
|
-
|
68
|
+
generations = 3
|
69
|
+
|
70
|
+
result = Array.new(pool_size * generations) do
|
64
71
|
Thread.new do
|
65
72
|
pool.with do |net|
|
66
73
|
net.do_something
|
67
74
|
end
|
68
75
|
end
|
69
|
-
end
|
76
|
+
end.map(&:value)
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
78
|
+
finish = Time.new
|
79
|
+
|
80
|
+
assert_equal((1..generations).cycle(pool_size).sort, result.sort)
|
81
|
+
|
82
|
+
assert_operator(finish - start, :>, generations * NetworkConnection::SLEEP_TIME)
|
76
83
|
end
|
77
84
|
|
78
85
|
def test_timeout
|
79
|
-
pool = ConnectionPool.new(:
|
86
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
80
87
|
thread = Thread.new do
|
81
88
|
pool.with do |net|
|
82
89
|
net.do_something
|
@@ -98,7 +105,7 @@ class TestConnectionPool < Minitest::Test
|
|
98
105
|
end
|
99
106
|
|
100
107
|
def test_with
|
101
|
-
pool = ConnectionPool.new(:
|
108
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
102
109
|
|
103
110
|
pool.with do
|
104
111
|
assert_raises Timeout::Error do
|
@@ -109,42 +116,62 @@ class TestConnectionPool < Minitest::Test
|
|
109
116
|
assert Thread.new { pool.checkout }.join
|
110
117
|
end
|
111
118
|
|
112
|
-
def
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
+
def test_with_timeout
|
120
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
121
|
+
|
122
|
+
assert_raises Timeout::Error do
|
123
|
+
Timeout.timeout(0.01) do
|
124
|
+
pool.with do |obj|
|
125
|
+
assert_equal 0, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
|
126
|
+
sleep 0.015
|
127
|
+
end
|
119
128
|
end
|
120
129
|
end
|
130
|
+
assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
|
131
|
+
end
|
121
132
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
# no "connections" allocated yet
|
126
|
-
assert_equal [], ObjectSpace.each_object(marker_class).to_a
|
133
|
+
def test_checkout_ignores_timeout
|
134
|
+
skip("Thread.handle_interrupt not available") unless Thread.respond_to?(:handle_interrupt)
|
127
135
|
|
128
|
-
|
136
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
137
|
+
def pool.checkout(options)
|
138
|
+
sleep 0.015
|
139
|
+
super
|
140
|
+
end
|
129
141
|
|
142
|
+
did_something = false
|
130
143
|
assert_raises Timeout::Error do
|
131
|
-
Timeout.timeout(
|
132
|
-
pool.with do
|
133
|
-
|
134
|
-
|
135
|
-
|
144
|
+
Timeout.timeout(0.01) do
|
145
|
+
pool.with do |obj|
|
146
|
+
did_something = true
|
147
|
+
# Timeout::Error will be triggered by any non-trivial Ruby code
|
148
|
+
# executed here since it couldn't be raised during checkout.
|
149
|
+
# It looks like setting the local variable above does not trigger
|
150
|
+
# the Timeout check in MRI 2.2.1.
|
151
|
+
obj.tap { obj.hash }
|
136
152
|
end
|
137
153
|
end
|
138
154
|
end
|
155
|
+
assert did_something
|
156
|
+
assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
|
157
|
+
end
|
139
158
|
|
140
|
-
|
159
|
+
def test_explicit_return
|
160
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) do
|
161
|
+
mock = Minitest::Mock.new
|
162
|
+
def mock.disconnect!
|
163
|
+
raise "should not disconnect upon explicit return"
|
164
|
+
end
|
165
|
+
mock
|
166
|
+
end
|
141
167
|
|
142
|
-
|
143
|
-
|
168
|
+
pool.with do |conn|
|
169
|
+
return true
|
170
|
+
end
|
144
171
|
end
|
145
172
|
|
146
173
|
def test_with_timeout_override
|
147
|
-
pool = ConnectionPool.new(:
|
174
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
148
175
|
|
149
176
|
t = Thread.new do
|
150
177
|
pool.with do |net|
|
@@ -159,13 +186,13 @@ class TestConnectionPool < Minitest::Test
|
|
159
186
|
pool.with { |net| net.do_something }
|
160
187
|
end
|
161
188
|
|
162
|
-
pool.with(:
|
189
|
+
pool.with(timeout: 2 * NetworkConnection::SLEEP_TIME) do |conn|
|
163
190
|
refute_nil conn
|
164
191
|
end
|
165
192
|
end
|
166
193
|
|
167
194
|
def test_checkin
|
168
|
-
pool = ConnectionPool.new(:
|
195
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
169
196
|
conn = pool.checkout
|
170
197
|
|
171
198
|
assert_raises Timeout::Error do
|
@@ -178,12 +205,12 @@ class TestConnectionPool < Minitest::Test
|
|
178
205
|
end
|
179
206
|
|
180
207
|
def test_returns_value
|
181
|
-
pool = ConnectionPool.new(:
|
208
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
182
209
|
assert_equal 1, pool.with {|o| 1 }
|
183
210
|
end
|
184
211
|
|
185
212
|
def test_checkin_never_checkout
|
186
|
-
pool = ConnectionPool.new(:
|
213
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
187
214
|
|
188
215
|
e = assert_raises ConnectionPool::Error do
|
189
216
|
pool.checkin
|
@@ -193,7 +220,7 @@ class TestConnectionPool < Minitest::Test
|
|
193
220
|
end
|
194
221
|
|
195
222
|
def test_checkin_no_current_checkout
|
196
|
-
pool = ConnectionPool.new(:
|
223
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
197
224
|
|
198
225
|
pool.checkout
|
199
226
|
pool.checkin
|
@@ -204,7 +231,7 @@ class TestConnectionPool < Minitest::Test
|
|
204
231
|
end
|
205
232
|
|
206
233
|
def test_checkin_twice
|
207
|
-
pool = ConnectionPool.new(:
|
234
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
208
235
|
|
209
236
|
pool.checkout
|
210
237
|
pool.checkout
|
@@ -223,7 +250,7 @@ class TestConnectionPool < Minitest::Test
|
|
223
250
|
end
|
224
251
|
|
225
252
|
def test_checkout
|
226
|
-
pool = ConnectionPool.new(:
|
253
|
+
pool = ConnectionPool.new(size: 1) { NetworkConnection.new }
|
227
254
|
|
228
255
|
conn = pool.checkout
|
229
256
|
|
@@ -233,7 +260,7 @@ class TestConnectionPool < Minitest::Test
|
|
233
260
|
end
|
234
261
|
|
235
262
|
def test_checkout_multithread
|
236
|
-
pool = ConnectionPool.new(:
|
263
|
+
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
|
237
264
|
conn = pool.checkout
|
238
265
|
|
239
266
|
t = Thread.new do
|
@@ -244,7 +271,7 @@ class TestConnectionPool < Minitest::Test
|
|
244
271
|
end
|
245
272
|
|
246
273
|
def test_checkout_timeout
|
247
|
-
pool = ConnectionPool.new(:
|
274
|
+
pool = ConnectionPool.new(timeout: 0, size: 0) { Object.new }
|
248
275
|
|
249
276
|
assert_raises Timeout::Error do
|
250
277
|
pool.checkout
|
@@ -252,7 +279,7 @@ class TestConnectionPool < Minitest::Test
|
|
252
279
|
end
|
253
280
|
|
254
281
|
def test_checkout_timeout_override
|
255
|
-
pool = ConnectionPool.new(:
|
282
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
256
283
|
|
257
284
|
thread = Thread.new do
|
258
285
|
pool.with do |net|
|
@@ -267,11 +294,11 @@ class TestConnectionPool < Minitest::Test
|
|
267
294
|
pool.checkout
|
268
295
|
end
|
269
296
|
|
270
|
-
assert pool.checkout :
|
297
|
+
assert pool.checkout timeout: 2 * NetworkConnection::SLEEP_TIME
|
271
298
|
end
|
272
299
|
|
273
300
|
def test_passthru
|
274
|
-
pool = ConnectionPool.wrap(:
|
301
|
+
pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
|
275
302
|
assert_equal 1, pool.do_something
|
276
303
|
assert_equal 2, pool.do_something
|
277
304
|
assert_equal 5, pool.do_something_with_block { 3 }
|
@@ -279,7 +306,7 @@ class TestConnectionPool < Minitest::Test
|
|
279
306
|
end
|
280
307
|
|
281
308
|
def test_passthru_respond_to
|
282
|
-
pool = ConnectionPool.wrap(:
|
309
|
+
pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
|
283
310
|
assert pool.respond_to?(:with)
|
284
311
|
assert pool.respond_to?(:do_something)
|
285
312
|
assert pool.respond_to?(:do_magic)
|
@@ -287,7 +314,7 @@ class TestConnectionPool < Minitest::Test
|
|
287
314
|
end
|
288
315
|
|
289
316
|
def test_return_value
|
290
|
-
pool = ConnectionPool.new(:
|
317
|
+
pool = ConnectionPool.new(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
|
291
318
|
result = pool.with do |net|
|
292
319
|
net.fast
|
293
320
|
end
|
@@ -295,7 +322,7 @@ class TestConnectionPool < Minitest::Test
|
|
295
322
|
end
|
296
323
|
|
297
324
|
def test_heavy_threading
|
298
|
-
pool = ConnectionPool.new(:
|
325
|
+
pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new }
|
299
326
|
|
300
327
|
threads = Array.new(20) do
|
301
328
|
Thread.new do
|
@@ -309,7 +336,7 @@ class TestConnectionPool < Minitest::Test
|
|
309
336
|
end
|
310
337
|
|
311
338
|
def test_reuses_objects_when_pool_not_saturated
|
312
|
-
pool = ConnectionPool.new(:
|
339
|
+
pool = ConnectionPool.new(size: 5) { NetworkConnection.new }
|
313
340
|
|
314
341
|
ids = 10.times.map do
|
315
342
|
pool.with { |c| c.object_id }
|
@@ -320,7 +347,7 @@ class TestConnectionPool < Minitest::Test
|
|
320
347
|
|
321
348
|
def test_nested_checkout
|
322
349
|
recorder = Recorder.new
|
323
|
-
pool = ConnectionPool.new(:
|
350
|
+
pool = ConnectionPool.new(size: 1) { recorder }
|
324
351
|
pool.with do |r_outer|
|
325
352
|
@other = Thread.new do |t|
|
326
353
|
pool.with do |r_other|
|
@@ -345,7 +372,7 @@ class TestConnectionPool < Minitest::Test
|
|
345
372
|
def test_shutdown_is_executed_for_all_connections
|
346
373
|
recorders = []
|
347
374
|
|
348
|
-
pool = ConnectionPool.new(:
|
375
|
+
pool = ConnectionPool.new(size: 3) do
|
349
376
|
Recorder.new.tap { |r| recorders << r }
|
350
377
|
end
|
351
378
|
|
@@ -361,7 +388,7 @@ class TestConnectionPool < Minitest::Test
|
|
361
388
|
end
|
362
389
|
|
363
390
|
def test_raises_error_after_shutting_down
|
364
|
-
pool = ConnectionPool.new(:
|
391
|
+
pool = ConnectionPool.new(size: 1) { true }
|
365
392
|
|
366
393
|
pool.shutdown { }
|
367
394
|
|
@@ -373,7 +400,7 @@ class TestConnectionPool < Minitest::Test
|
|
373
400
|
def test_runs_shutdown_block_asynchronously_if_connection_was_in_use
|
374
401
|
recorders = []
|
375
402
|
|
376
|
-
pool = ConnectionPool.new(:
|
403
|
+
pool = ConnectionPool.new(size: 3) do
|
377
404
|
Recorder.new.tap { |r| recorders << r }
|
378
405
|
end
|
379
406
|
|
@@ -395,7 +422,7 @@ class TestConnectionPool < Minitest::Test
|
|
395
422
|
end
|
396
423
|
|
397
424
|
def test_raises_an_error_if_shutdown_is_called_without_a_block
|
398
|
-
pool = ConnectionPool.new(:
|
425
|
+
pool = ConnectionPool.new(size: 1) { }
|
399
426
|
|
400
427
|
assert_raises ArgumentError do
|
401
428
|
pool.shutdown
|
@@ -405,7 +432,7 @@ class TestConnectionPool < Minitest::Test
|
|
405
432
|
def test_shutdown_is_executed_for_all_connections_in_wrapped_pool
|
406
433
|
recorders = []
|
407
434
|
|
408
|
-
wrapper = ConnectionPool::Wrapper.new(:
|
435
|
+
wrapper = ConnectionPool::Wrapper.new(size: 3) do
|
409
436
|
Recorder.new.tap { |r| recorders << r }
|
410
437
|
end
|
411
438
|
|
@@ -436,7 +463,7 @@ class TestConnectionPool < Minitest::Test
|
|
436
463
|
end
|
437
464
|
|
438
465
|
def test_wrapper_with
|
439
|
-
wrapper = ConnectionPool::Wrapper.new(:
|
466
|
+
wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { Object.new }
|
440
467
|
|
441
468
|
wrapper.with do
|
442
469
|
assert_raises Timeout::Error do
|
@@ -461,4 +488,29 @@ class TestConnectionPool < Minitest::Test
|
|
461
488
|
assert_equal "eval'ed 1", wrapper.eval(1)
|
462
489
|
end
|
463
490
|
|
491
|
+
def test_wrapper_with_connection_pool
|
492
|
+
recorder = Recorder.new
|
493
|
+
pool = ConnectionPool.new(size: 1) { recorder }
|
494
|
+
wrapper = ConnectionPool::Wrapper.new(pool: pool)
|
495
|
+
|
496
|
+
pool.with { |r| r.do_work('with') }
|
497
|
+
wrapper.do_work('wrapped')
|
498
|
+
|
499
|
+
assert_equal ['with', 'wrapped'], recorder.calls
|
500
|
+
end
|
501
|
+
|
502
|
+
def test_stats_without_active_connection
|
503
|
+
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
|
504
|
+
|
505
|
+
assert_equal(2, pool.size)
|
506
|
+
assert_equal(2, pool.available)
|
507
|
+
end
|
508
|
+
|
509
|
+
def test_stats_with_active_connection
|
510
|
+
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
|
511
|
+
|
512
|
+
pool.with do
|
513
|
+
assert_equal(1, pool.available)
|
514
|
+
end
|
515
|
+
end
|
464
516
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: connection_pool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Perham
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2018-05-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -70,6 +70,7 @@ files:
|
|
70
70
|
- Rakefile
|
71
71
|
- connection_pool.gemspec
|
72
72
|
- lib/connection_pool.rb
|
73
|
+
- lib/connection_pool/monotonic_time.rb
|
73
74
|
- lib/connection_pool/timed_stack.rb
|
74
75
|
- lib/connection_pool/version.rb
|
75
76
|
- test/helper.rb
|
@@ -95,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
96
|
version: '0'
|
96
97
|
requirements: []
|
97
98
|
rubyforge_project:
|
98
|
-
rubygems_version: 2.
|
99
|
+
rubygems_version: 2.6.13
|
99
100
|
signing_key:
|
100
101
|
specification_version: 4
|
101
102
|
summary: Generic connection pool for Ruby
|