connection_pool 2.2.2 → 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 +5 -5
- data/.travis.yml +7 -5
- data/Changes.md +9 -2
- data/Gemfile +2 -0
- data/README.md +15 -1
- data/Rakefile +4 -3
- data/connection_pool.gemspec +13 -14
- data/lib/connection_pool.rb +23 -74
- data/lib/connection_pool/timed_stack.rb +8 -14
- data/lib/connection_pool/version.rb +1 -1
- data/lib/connection_pool/wrapper.rb +43 -0
- data/test/helper.rb +4 -4
- data/test/test_connection_pool.rb +110 -73
- data/test/test_connection_pool_timed_stack.rb +12 -21
- metadata +4 -5
- data/lib/connection_pool/monotonic_time.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: e9fefa40cd9db0f5add54482a9f980d808b5bb578e83a5a8bf287c636c41704d
|
4
|
+
data.tar.gz: ec25d36c42cfb863e768bf49b18316d955397edaa8862f92d27687ab2a089bf0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8653437078b6334be998d16f4851e12a21e8caca8338a07559c9bfbe646dc6023a7370aad6e33be905b05d6eaab8f249387eaa72b16a478f57c9c497adbbf30
|
7
|
+
data.tar.gz: 2a437d085a3f11376338ee32debe5e7ff38fc28fdc8d0d318287c2890bc90ce2d282468981dde43c8391798d113e1391d7998a5f5e571f1021ae99dfff6893e2
|
data/.travis.yml
CHANGED
data/Changes.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# connection_pool Changelog
|
2
|
+
|
3
|
+
2.2.3
|
4
|
+
------
|
5
|
+
|
6
|
+
- Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130]
|
7
|
+
- Use monotonic clock present in all modern Rubies [Tero Tasanen, #109]
|
8
|
+
- Remove code hacks necessary for JRuby 1.7
|
9
|
+
- Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113]
|
3
10
|
|
4
11
|
2.2.2
|
5
12
|
------
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -51,7 +51,7 @@ You can use `ConnectionPool::Wrapper` to wrap a single global connection,
|
|
51
51
|
making it easier to migrate existing connection code over time:
|
52
52
|
|
53
53
|
``` ruby
|
54
|
-
$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.
|
54
|
+
$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
|
55
55
|
$redis.sadd('foo', 1)
|
56
56
|
$redis.smembers('foo')
|
57
57
|
```
|
@@ -87,6 +87,20 @@ Shutting down a connection pool will block until all connections are checked in
|
|
87
87
|
**Note that shutting down is completely optional**; Ruby's garbage collector will reclaim
|
88
88
|
unreferenced pools under normal circumstances.
|
89
89
|
|
90
|
+
## Current State
|
91
|
+
|
92
|
+
There are several methods that return information about a pool.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
cp = ConnectionPool.new(size: 10) { Redis.new }
|
96
|
+
cp.size # => 10
|
97
|
+
cp.available # => 10
|
98
|
+
|
99
|
+
cp.with do |conn|
|
100
|
+
cp.size # => 10
|
101
|
+
cp.available # => 9
|
102
|
+
end
|
103
|
+
```
|
90
104
|
|
91
105
|
Notes
|
92
106
|
-----
|
data/Rakefile
CHANGED
data/connection_pool.gemspec
CHANGED
@@ -1,21 +1,20 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
1
|
require "./lib/connection_pool/version"
|
3
2
|
|
4
3
|
Gem::Specification.new do |s|
|
5
|
-
s.name
|
6
|
-
s.version
|
7
|
-
s.platform
|
8
|
-
s.authors
|
9
|
-
s.email
|
10
|
-
s.homepage
|
11
|
-
s.description = s.summary =
|
4
|
+
s.name = "connection_pool"
|
5
|
+
s.version = ConnectionPool::VERSION
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.authors = ["Mike Perham", "Damian Janowski"]
|
8
|
+
s.email = ["mperham@gmail.com", "damian@educabilia.com"]
|
9
|
+
s.homepage = "https://github.com/mperham/connection_pool"
|
10
|
+
s.description = s.summary = "Generic connection pool for Ruby"
|
12
11
|
|
13
|
-
s.files
|
14
|
-
s.test_files
|
15
|
-
s.executables
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
16
15
|
s.require_paths = ["lib"]
|
17
16
|
s.license = "MIT"
|
18
|
-
s.add_development_dependency
|
19
|
-
s.add_development_dependency
|
20
|
-
s.add_development_dependency
|
17
|
+
s.add_development_dependency "bundler"
|
18
|
+
s.add_development_dependency "minitest", ">= 5.0.0"
|
19
|
+
s.add_development_dependency "rake"
|
21
20
|
end
|
data/lib/connection_pool.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require "timeout"
|
2
|
+
require "connection_pool/version"
|
3
3
|
|
4
|
+
class ConnectionPool
|
5
|
+
class Error < ::RuntimeError; end
|
6
|
+
class PoolShuttingDownError < ::ConnectionPool::Error; end
|
7
|
+
class TimeoutError < ::Timeout::Error; end
|
8
|
+
end
|
4
9
|
|
5
|
-
# Generic connection pool class for
|
6
|
-
# among many threads. Note:
|
10
|
+
# Generic connection pool class for sharing a limited number of objects or network connections
|
11
|
+
# among many threads. Note: pool elements are lazily created.
|
7
12
|
#
|
8
13
|
# Example usage with block (faster):
|
9
14
|
#
|
10
15
|
# @pool = ConnectionPool.new { Redis.new }
|
11
|
-
#
|
12
16
|
# @pool.with do |redis|
|
13
17
|
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
14
18
|
# end
|
@@ -34,29 +38,23 @@ require_relative 'connection_pool/timed_stack'
|
|
34
38
|
class ConnectionPool
|
35
39
|
DEFAULTS = {size: 5, timeout: 5}
|
36
40
|
|
37
|
-
class Error < RuntimeError
|
38
|
-
end
|
39
|
-
|
40
41
|
def self.wrap(options, &block)
|
41
42
|
Wrapper.new(options, &block)
|
42
43
|
end
|
43
44
|
|
44
45
|
def initialize(options = {}, &block)
|
45
|
-
raise ArgumentError,
|
46
|
+
raise ArgumentError, "Connection pool requires a block" unless block
|
46
47
|
|
47
48
|
options = DEFAULTS.merge(options)
|
48
49
|
|
49
|
-
@size = options.fetch(:size)
|
50
|
+
@size = Integer(options.fetch(:size))
|
50
51
|
@timeout = options.fetch(:timeout)
|
51
52
|
|
52
53
|
@available = TimedStack.new(@size, &block)
|
53
|
-
@key = :"
|
54
|
-
@key_count = :"
|
54
|
+
@key = :"pool-#{@available.object_id}"
|
55
|
+
@key_count = :"pool-#{@available.object_id}-count"
|
55
56
|
end
|
56
57
|
|
57
|
-
if Thread.respond_to?(:handle_interrupt)
|
58
|
-
|
59
|
-
# MRI
|
60
58
|
def with(options = {})
|
61
59
|
Thread.handle_interrupt(Exception => :never) do
|
62
60
|
conn = checkout(options)
|
@@ -70,27 +68,13 @@ if Thread.respond_to?(:handle_interrupt)
|
|
70
68
|
end
|
71
69
|
end
|
72
70
|
|
73
|
-
else
|
74
|
-
|
75
|
-
# jruby 1.7.x
|
76
|
-
def with(options = {})
|
77
|
-
conn = checkout(options)
|
78
|
-
begin
|
79
|
-
yield conn
|
80
|
-
ensure
|
81
|
-
checkin
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
end
|
86
|
-
|
87
71
|
def checkout(options = {})
|
88
72
|
if ::Thread.current[@key]
|
89
|
-
::Thread.current[@key_count]+= 1
|
73
|
+
::Thread.current[@key_count] += 1
|
90
74
|
::Thread.current[@key]
|
91
75
|
else
|
92
|
-
::Thread.current[@key_count]= 1
|
93
|
-
::Thread.current[@key]= @available.pop(options[:timeout] || @timeout)
|
76
|
+
::Thread.current[@key_count] = 1
|
77
|
+
::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
|
94
78
|
end
|
95
79
|
end
|
96
80
|
|
@@ -98,12 +82,12 @@ end
|
|
98
82
|
if ::Thread.current[@key]
|
99
83
|
if ::Thread.current[@key_count] == 1
|
100
84
|
@available.push(::Thread.current[@key])
|
101
|
-
::Thread.current[@key]= nil
|
85
|
+
::Thread.current[@key] = nil
|
102
86
|
else
|
103
|
-
::Thread.current[@key_count]-= 1
|
87
|
+
::Thread.current[@key_count] -= 1
|
104
88
|
end
|
105
89
|
else
|
106
|
-
raise ConnectionPool::Error,
|
90
|
+
raise ConnectionPool::Error, "no connections are checked out"
|
107
91
|
end
|
108
92
|
|
109
93
|
nil
|
@@ -114,48 +98,13 @@ end
|
|
114
98
|
end
|
115
99
|
|
116
100
|
# Size of this connection pool
|
117
|
-
|
118
|
-
@size
|
119
|
-
end
|
101
|
+
attr_reader :size
|
120
102
|
|
121
103
|
# Number of pool entries available for checkout at this instant.
|
122
104
|
def available
|
123
105
|
@available.length
|
124
106
|
end
|
125
|
-
|
126
|
-
private
|
127
|
-
|
128
|
-
class Wrapper < ::BasicObject
|
129
|
-
METHODS = [:with, :pool_shutdown]
|
130
|
-
|
131
|
-
def initialize(options = {}, &block)
|
132
|
-
@pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) }
|
133
|
-
end
|
134
|
-
|
135
|
-
def with(&block)
|
136
|
-
@pool.with(&block)
|
137
|
-
end
|
138
|
-
|
139
|
-
def pool_shutdown(&block)
|
140
|
-
@pool.shutdown(&block)
|
141
|
-
end
|
142
|
-
|
143
|
-
def pool_size
|
144
|
-
@pool.size
|
145
|
-
end
|
146
|
-
|
147
|
-
def pool_available
|
148
|
-
@pool.available
|
149
|
-
end
|
150
|
-
|
151
|
-
def respond_to?(id, *args)
|
152
|
-
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
153
|
-
end
|
154
|
-
|
155
|
-
def method_missing(name, *args, &block)
|
156
|
-
with do |connection|
|
157
|
-
connection.send(name, *args, &block)
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
107
|
end
|
108
|
+
|
109
|
+
require "connection_pool/timed_stack"
|
110
|
+
require "connection_pool/wrapper"
|
@@ -1,13 +1,3 @@
|
|
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
1
|
##
|
12
2
|
# The TimedStack manages a pool of homogeneous connections (or any resource
|
13
3
|
# you wish to manage). Connections are created lazily up to a given maximum
|
@@ -59,7 +49,7 @@ class ConnectionPool::TimedStack
|
|
59
49
|
@resource.broadcast
|
60
50
|
end
|
61
51
|
end
|
62
|
-
|
52
|
+
alias << push
|
63
53
|
|
64
54
|
##
|
65
55
|
# Retrieves a connection from the stack. If a connection is available it is
|
@@ -74,7 +64,7 @@ class ConnectionPool::TimedStack
|
|
74
64
|
options, timeout = timeout, 0.5 if Hash === timeout
|
75
65
|
timeout = options.fetch :timeout, timeout
|
76
66
|
|
77
|
-
deadline =
|
67
|
+
deadline = current_time + timeout
|
78
68
|
@mutex.synchronize do
|
79
69
|
loop do
|
80
70
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
@@ -83,8 +73,8 @@ class ConnectionPool::TimedStack
|
|
83
73
|
connection = try_create(options)
|
84
74
|
return connection if connection
|
85
75
|
|
86
|
-
to_wait = deadline -
|
87
|
-
raise
|
76
|
+
to_wait = deadline - current_time
|
77
|
+
raise ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0
|
88
78
|
@resource.wait(@mutex, to_wait)
|
89
79
|
end
|
90
80
|
end
|
@@ -121,6 +111,10 @@ class ConnectionPool::TimedStack
|
|
121
111
|
|
122
112
|
private
|
123
113
|
|
114
|
+
def current_time
|
115
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
116
|
+
end
|
117
|
+
|
124
118
|
##
|
125
119
|
# This is an extension point for TimedStack and is called with a mutex.
|
126
120
|
#
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class ConnectionPool
|
2
|
+
class Wrapper < ::BasicObject
|
3
|
+
METHODS = [:with, :pool_shutdown, :wrapped_pool]
|
4
|
+
|
5
|
+
def initialize(options = {}, &block)
|
6
|
+
@pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) }
|
7
|
+
end
|
8
|
+
|
9
|
+
def wrapped_pool
|
10
|
+
@pool
|
11
|
+
end
|
12
|
+
|
13
|
+
def with(&block)
|
14
|
+
@pool.with(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def pool_shutdown(&block)
|
18
|
+
@pool.shutdown(&block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def pool_size
|
22
|
+
@pool.size
|
23
|
+
end
|
24
|
+
|
25
|
+
def pool_available
|
26
|
+
@pool.available
|
27
|
+
end
|
28
|
+
|
29
|
+
def respond_to?(id, *args)
|
30
|
+
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
31
|
+
end
|
32
|
+
|
33
|
+
# rubocop:disable Style/MethodMissingSuper
|
34
|
+
# rubocop:disable Style/MissingRespondToMissing
|
35
|
+
def method_missing(name, *args, &block)
|
36
|
+
with do |connection|
|
37
|
+
connection.send(name, *args, &block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# rubocop:enable Style/MethodMissingSuper
|
41
|
+
# rubocop:enable Style/MissingRespondToMissing
|
42
|
+
end
|
43
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "helper"
|
2
2
|
|
3
3
|
class TestConnectionPool < Minitest::Test
|
4
|
-
|
5
4
|
class NetworkConnection
|
6
5
|
SLEEP_TIME = 0.1
|
7
6
|
|
@@ -43,12 +42,12 @@ class TestConnectionPool < Minitest::Test
|
|
43
42
|
end
|
44
43
|
|
45
44
|
def use_pool(pool, size)
|
46
|
-
Array.new(size)
|
45
|
+
Array.new(size) {
|
47
46
|
Thread.new do
|
48
|
-
pool.with
|
47
|
+
pool.with { sleep }
|
49
48
|
end
|
50
|
-
|
51
|
-
Thread.pass until thread.status ==
|
49
|
+
}.each do |thread|
|
50
|
+
Thread.pass until thread.status == "sleep"
|
52
51
|
end
|
53
52
|
end
|
54
53
|
|
@@ -67,13 +66,13 @@ class TestConnectionPool < Minitest::Test
|
|
67
66
|
|
68
67
|
generations = 3
|
69
68
|
|
70
|
-
result = Array.new(pool_size * generations)
|
69
|
+
result = Array.new(pool_size * generations) {
|
71
70
|
Thread.new do
|
72
71
|
pool.with do |net|
|
73
72
|
net.do_something
|
74
73
|
end
|
75
74
|
end
|
76
|
-
|
75
|
+
}.map(&:value)
|
77
76
|
|
78
77
|
finish = Time.new
|
79
78
|
|
@@ -84,14 +83,14 @@ class TestConnectionPool < Minitest::Test
|
|
84
83
|
|
85
84
|
def test_timeout
|
86
85
|
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
87
|
-
thread = Thread.new
|
86
|
+
thread = Thread.new {
|
88
87
|
pool.with do |net|
|
89
88
|
net.do_something
|
90
89
|
sleep 0.01
|
91
90
|
end
|
92
|
-
|
91
|
+
}
|
93
92
|
|
94
|
-
Thread.pass while thread.status ==
|
93
|
+
Thread.pass while thread.status == "run"
|
95
94
|
|
96
95
|
assert_raises Timeout::Error do
|
97
96
|
pool.with { |net| net.do_something }
|
@@ -108,9 +107,11 @@ class TestConnectionPool < Minitest::Test
|
|
108
107
|
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
109
108
|
|
110
109
|
pool.with do
|
111
|
-
|
112
|
-
|
113
|
-
|
110
|
+
Thread.new {
|
111
|
+
assert_raises Timeout::Error do
|
112
|
+
pool.checkout
|
113
|
+
end
|
114
|
+
}.join
|
114
115
|
end
|
115
116
|
|
116
117
|
assert Thread.new { pool.checkout }.join
|
@@ -122,17 +123,24 @@ class TestConnectionPool < Minitest::Test
|
|
122
123
|
assert_raises Timeout::Error do
|
123
124
|
Timeout.timeout(0.01) do
|
124
125
|
pool.with do |obj|
|
125
|
-
assert_equal 0, pool.
|
126
|
+
assert_equal 0, pool.available
|
126
127
|
sleep 0.015
|
127
128
|
end
|
128
129
|
end
|
129
130
|
end
|
130
|
-
assert_equal 1, pool.
|
131
|
+
assert_equal 1, pool.available
|
131
132
|
end
|
132
133
|
|
133
|
-
def
|
134
|
-
|
134
|
+
def test_invalid_size
|
135
|
+
assert_raises ArgumentError, TypeError do
|
136
|
+
ConnectionPool.new(timeout: 0, size: nil) { Object.new }
|
137
|
+
end
|
138
|
+
assert_raises ArgumentError, TypeError do
|
139
|
+
ConnectionPool.new(timeout: 0, size: "") { Object.new }
|
140
|
+
end
|
141
|
+
end
|
135
142
|
|
143
|
+
def test_handle_interrupt_ensures_checkin
|
136
144
|
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
137
145
|
def pool.checkout(options)
|
138
146
|
sleep 0.015
|
@@ -140,7 +148,8 @@ class TestConnectionPool < Minitest::Test
|
|
140
148
|
end
|
141
149
|
|
142
150
|
did_something = false
|
143
|
-
|
151
|
+
|
152
|
+
action = lambda do
|
144
153
|
Timeout.timeout(0.01) do
|
145
154
|
pool.with do |obj|
|
146
155
|
did_something = true
|
@@ -152,18 +161,33 @@ class TestConnectionPool < Minitest::Test
|
|
152
161
|
end
|
153
162
|
end
|
154
163
|
end
|
155
|
-
|
156
|
-
|
164
|
+
|
165
|
+
if RUBY_ENGINE == "ruby"
|
166
|
+
# These asserts rely on the Ruby implementation reaching `did_something =
|
167
|
+
# true` before the interrupt is detected by the thread. Interrupt
|
168
|
+
# detection timing is implementation-specific in practice, with JRuby,
|
169
|
+
# Rubinius, and TruffleRuby all having different interrupt timings to MRI.
|
170
|
+
# In fact they generally detect interrupts more quickly than MRI, so they
|
171
|
+
# may not reach `did_something = true` before detecting the interrupt.
|
172
|
+
|
173
|
+
assert_raises Timeout::Error, &action
|
174
|
+
|
175
|
+
assert did_something
|
176
|
+
else
|
177
|
+
action.call
|
178
|
+
end
|
179
|
+
|
180
|
+
assert_equal 1, pool.available
|
157
181
|
end
|
158
182
|
|
159
183
|
def test_explicit_return
|
160
|
-
pool = ConnectionPool.new(timeout: 0, size: 1)
|
184
|
+
pool = ConnectionPool.new(timeout: 0, size: 1) {
|
161
185
|
mock = Minitest::Mock.new
|
162
186
|
def mock.disconnect!
|
163
187
|
raise "should not disconnect upon explicit return"
|
164
188
|
end
|
165
189
|
mock
|
166
|
-
|
190
|
+
}
|
167
191
|
|
168
192
|
pool.with do |conn|
|
169
193
|
return true
|
@@ -173,14 +197,14 @@ class TestConnectionPool < Minitest::Test
|
|
173
197
|
def test_with_timeout_override
|
174
198
|
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
175
199
|
|
176
|
-
t = Thread.new
|
200
|
+
t = Thread.new {
|
177
201
|
pool.with do |net|
|
178
202
|
net.do_something
|
179
203
|
sleep 0.01
|
180
204
|
end
|
181
|
-
|
205
|
+
}
|
182
206
|
|
183
|
-
Thread.pass while t.status ==
|
207
|
+
Thread.pass while t.status == "run"
|
184
208
|
|
185
209
|
assert_raises Timeout::Error do
|
186
210
|
pool.with { |net| net.do_something }
|
@@ -195,9 +219,11 @@ class TestConnectionPool < Minitest::Test
|
|
195
219
|
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
196
220
|
conn = pool.checkout
|
197
221
|
|
198
|
-
|
199
|
-
|
200
|
-
|
222
|
+
Thread.new {
|
223
|
+
assert_raises Timeout::Error do
|
224
|
+
pool.checkout
|
225
|
+
end
|
226
|
+
}.join
|
201
227
|
|
202
228
|
pool.checkin
|
203
229
|
|
@@ -206,17 +232,14 @@ class TestConnectionPool < Minitest::Test
|
|
206
232
|
|
207
233
|
def test_returns_value
|
208
234
|
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
209
|
-
assert_equal 1, pool.with {|o| 1 }
|
235
|
+
assert_equal 1, pool.with { |o| 1 }
|
210
236
|
end
|
211
237
|
|
212
238
|
def test_checkin_never_checkout
|
213
239
|
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
|
214
240
|
|
215
|
-
e = assert_raises
|
216
|
-
|
217
|
-
end
|
218
|
-
|
219
|
-
assert_equal 'no connections are checked out', e.message
|
241
|
+
e = assert_raises(ConnectionPool::Error) { pool.checkin }
|
242
|
+
assert_equal "no connections are checked out", e.message
|
220
243
|
end
|
221
244
|
|
222
245
|
def test_checkin_no_current_checkout
|
@@ -238,11 +261,11 @@ class TestConnectionPool < Minitest::Test
|
|
238
261
|
|
239
262
|
pool.checkin
|
240
263
|
|
241
|
-
|
242
|
-
|
264
|
+
Thread.new {
|
265
|
+
assert_raises Timeout::Error do
|
243
266
|
pool.checkout
|
244
|
-
end
|
245
|
-
|
267
|
+
end
|
268
|
+
}.join
|
246
269
|
|
247
270
|
pool.checkin
|
248
271
|
|
@@ -263,9 +286,9 @@ class TestConnectionPool < Minitest::Test
|
|
263
286
|
pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
|
264
287
|
conn = pool.checkout
|
265
288
|
|
266
|
-
t = Thread.new
|
289
|
+
t = Thread.new {
|
267
290
|
pool.checkout
|
268
|
-
|
291
|
+
}
|
269
292
|
|
270
293
|
refute_same conn, t.value
|
271
294
|
end
|
@@ -281,14 +304,14 @@ class TestConnectionPool < Minitest::Test
|
|
281
304
|
def test_checkout_timeout_override
|
282
305
|
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
|
283
306
|
|
284
|
-
thread = Thread.new
|
307
|
+
thread = Thread.new {
|
285
308
|
pool.with do |net|
|
286
309
|
net.do_something
|
287
310
|
sleep 0.01
|
288
311
|
end
|
289
|
-
|
312
|
+
}
|
290
313
|
|
291
|
-
Thread.pass while thread.status ==
|
314
|
+
Thread.pass while thread.status == "run"
|
292
315
|
|
293
316
|
assert_raises Timeout::Error do
|
294
317
|
pool.checkout
|
@@ -315,22 +338,22 @@ class TestConnectionPool < Minitest::Test
|
|
315
338
|
|
316
339
|
def test_return_value
|
317
340
|
pool = ConnectionPool.new(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
|
318
|
-
result = pool.with
|
341
|
+
result = pool.with { |net|
|
319
342
|
net.fast
|
320
|
-
|
343
|
+
}
|
321
344
|
assert_equal 1, result
|
322
345
|
end
|
323
346
|
|
324
347
|
def test_heavy_threading
|
325
348
|
pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new }
|
326
349
|
|
327
|
-
threads = Array.new(20)
|
350
|
+
threads = Array.new(20) {
|
328
351
|
Thread.new do
|
329
352
|
pool.with do |net|
|
330
353
|
sleep 0.01
|
331
354
|
end
|
332
355
|
end
|
333
|
-
|
356
|
+
}
|
334
357
|
|
335
358
|
threads.map { |thread| thread.join }
|
336
359
|
end
|
@@ -338,9 +361,9 @@ class TestConnectionPool < Minitest::Test
|
|
338
361
|
def test_reuses_objects_when_pool_not_saturated
|
339
362
|
pool = ConnectionPool.new(size: 5) { NetworkConnection.new }
|
340
363
|
|
341
|
-
ids = 10.times.map
|
364
|
+
ids = 10.times.map {
|
342
365
|
pool.with { |c| c.object_id }
|
343
|
-
|
366
|
+
}
|
344
367
|
|
345
368
|
assert_equal 1, ids.uniq.size
|
346
369
|
end
|
@@ -349,32 +372,32 @@ class TestConnectionPool < Minitest::Test
|
|
349
372
|
recorder = Recorder.new
|
350
373
|
pool = ConnectionPool.new(size: 1) { recorder }
|
351
374
|
pool.with do |r_outer|
|
352
|
-
@other = Thread.new
|
375
|
+
@other = Thread.new { |t|
|
353
376
|
pool.with do |r_other|
|
354
|
-
r_other.do_work(
|
377
|
+
r_other.do_work("other")
|
355
378
|
end
|
356
|
-
|
379
|
+
}
|
357
380
|
|
358
381
|
pool.with do |r_inner|
|
359
|
-
r_inner.do_work(
|
382
|
+
r_inner.do_work("inner")
|
360
383
|
end
|
361
384
|
|
362
385
|
Thread.pass
|
363
386
|
|
364
|
-
r_outer.do_work(
|
387
|
+
r_outer.do_work("outer")
|
365
388
|
end
|
366
389
|
|
367
390
|
@other.join
|
368
391
|
|
369
|
-
assert_equal [
|
392
|
+
assert_equal ["inner", "outer", "other"], recorder.calls
|
370
393
|
end
|
371
394
|
|
372
395
|
def test_shutdown_is_executed_for_all_connections
|
373
396
|
recorders = []
|
374
397
|
|
375
|
-
pool = ConnectionPool.new(size: 3)
|
398
|
+
pool = ConnectionPool.new(size: 3) {
|
376
399
|
Recorder.new.tap { |r| recorders << r }
|
377
|
-
|
400
|
+
}
|
378
401
|
|
379
402
|
threads = use_pool pool, 3
|
380
403
|
|
@@ -390,7 +413,7 @@ class TestConnectionPool < Minitest::Test
|
|
390
413
|
def test_raises_error_after_shutting_down
|
391
414
|
pool = ConnectionPool.new(size: 1) { true }
|
392
415
|
|
393
|
-
pool.shutdown {
|
416
|
+
pool.shutdown {}
|
394
417
|
|
395
418
|
assert_raises ConnectionPool::PoolShuttingDownError do
|
396
419
|
pool.checkout
|
@@ -400,9 +423,9 @@ class TestConnectionPool < Minitest::Test
|
|
400
423
|
def test_runs_shutdown_block_asynchronously_if_connection_was_in_use
|
401
424
|
recorders = []
|
402
425
|
|
403
|
-
pool = ConnectionPool.new(size: 3)
|
426
|
+
pool = ConnectionPool.new(size: 3) {
|
404
427
|
Recorder.new.tap { |r| recorders << r }
|
405
|
-
|
428
|
+
}
|
406
429
|
|
407
430
|
threads = use_pool pool, 2
|
408
431
|
|
@@ -422,7 +445,7 @@ class TestConnectionPool < Minitest::Test
|
|
422
445
|
end
|
423
446
|
|
424
447
|
def test_raises_an_error_if_shutdown_is_called_without_a_block
|
425
|
-
pool = ConnectionPool.new(size: 1) {
|
448
|
+
pool = ConnectionPool.new(size: 1) {}
|
426
449
|
|
427
450
|
assert_raises ArgumentError do
|
428
451
|
pool.shutdown
|
@@ -432,9 +455,9 @@ class TestConnectionPool < Minitest::Test
|
|
432
455
|
def test_shutdown_is_executed_for_all_connections_in_wrapped_pool
|
433
456
|
recorders = []
|
434
457
|
|
435
|
-
wrapper = ConnectionPool::Wrapper.new(size: 3)
|
458
|
+
wrapper = ConnectionPool::Wrapper.new(size: 3) {
|
436
459
|
Recorder.new.tap { |r| recorders << r }
|
437
|
-
|
460
|
+
}
|
438
461
|
|
439
462
|
threads = use_pool wrapper, 3
|
440
463
|
|
@@ -447,6 +470,11 @@ class TestConnectionPool < Minitest::Test
|
|
447
470
|
assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
|
448
471
|
end
|
449
472
|
|
473
|
+
def test_wrapper_wrapped_pool
|
474
|
+
wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
|
475
|
+
assert_equal ConnectionPool, wrapper.wrapped_pool.class
|
476
|
+
end
|
477
|
+
|
450
478
|
def test_wrapper_method_missing
|
451
479
|
wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
|
452
480
|
|
@@ -466,14 +494,14 @@ class TestConnectionPool < Minitest::Test
|
|
466
494
|
wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { Object.new }
|
467
495
|
|
468
496
|
wrapper.with do
|
469
|
-
|
470
|
-
|
471
|
-
wrapper.with { flunk
|
472
|
-
end
|
473
|
-
|
497
|
+
Thread.new {
|
498
|
+
assert_raises Timeout::Error do
|
499
|
+
wrapper.with { flunk "connection checked out :(" }
|
500
|
+
end
|
501
|
+
}.join
|
474
502
|
end
|
475
503
|
|
476
|
-
assert Thread.new { wrapper.with {
|
504
|
+
assert Thread.new { wrapper.with {} }.join
|
477
505
|
end
|
478
506
|
|
479
507
|
class ConnWithEval
|
@@ -493,10 +521,10 @@ class TestConnectionPool < Minitest::Test
|
|
493
521
|
pool = ConnectionPool.new(size: 1) { recorder }
|
494
522
|
wrapper = ConnectionPool::Wrapper.new(pool: pool)
|
495
523
|
|
496
|
-
pool.with { |r| r.do_work(
|
497
|
-
wrapper.do_work(
|
524
|
+
pool.with { |r| r.do_work("with") }
|
525
|
+
wrapper.do_work("wrapped")
|
498
526
|
|
499
|
-
assert_equal [
|
527
|
+
assert_equal ["with", "wrapped"], recorder.calls
|
500
528
|
end
|
501
529
|
|
502
530
|
def test_stats_without_active_connection
|
@@ -513,4 +541,13 @@ class TestConnectionPool < Minitest::Test
|
|
513
541
|
assert_equal(1, pool.available)
|
514
542
|
end
|
515
543
|
end
|
544
|
+
|
545
|
+
def test_stats_with_string_size
|
546
|
+
pool = ConnectionPool.new(size: "2") { NetworkConnection.new }
|
547
|
+
|
548
|
+
pool.with do
|
549
|
+
assert_equal(2, pool.size)
|
550
|
+
assert_equal(1, pool.available)
|
551
|
+
end
|
552
|
+
end
|
516
553
|
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require_relative
|
1
|
+
require_relative "helper"
|
2
2
|
|
3
3
|
class TestConnectionPoolTimedStack < Minitest::Test
|
4
|
-
|
5
4
|
def setup
|
6
5
|
@stack = ConnectionPool::TimedStack.new { Object.new }
|
7
6
|
end
|
@@ -35,18 +34,18 @@ class TestConnectionPoolTimedStack < Minitest::Test
|
|
35
34
|
end
|
36
35
|
|
37
36
|
def test_object_creation_fails
|
38
|
-
stack = ConnectionPool::TimedStack.new(2) { raise
|
37
|
+
stack = ConnectionPool::TimedStack.new(2) { raise "failure" }
|
39
38
|
|
40
39
|
begin
|
41
40
|
stack.pop
|
42
41
|
rescue => error
|
43
|
-
assert_equal
|
42
|
+
assert_equal "failure", error.message
|
44
43
|
end
|
45
44
|
|
46
45
|
begin
|
47
46
|
stack.pop
|
48
47
|
rescue => error
|
49
|
-
assert_equal
|
48
|
+
assert_equal "failure", error.message
|
50
49
|
end
|
51
50
|
|
52
51
|
refute_empty stack
|
@@ -63,19 +62,13 @@ class TestConnectionPoolTimedStack < Minitest::Test
|
|
63
62
|
end
|
64
63
|
|
65
64
|
def test_pop_empty
|
66
|
-
e = assert_raises
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
assert_equal 'Waited 0 sec', e.message
|
65
|
+
e = assert_raises(ConnectionPool::TimeoutError) { @stack.pop timeout: 0 }
|
66
|
+
assert_equal "Waited 0 sec", e.message
|
71
67
|
end
|
72
68
|
|
73
69
|
def test_pop_empty_2_0_compatibility
|
74
|
-
e = assert_raises
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
assert_equal 'Waited 0 sec', e.message
|
70
|
+
e = assert_raises(Timeout::Error) { @stack.pop 0 }
|
71
|
+
assert_equal "Waited 0 sec", e.message
|
79
72
|
end
|
80
73
|
|
81
74
|
def test_pop_full
|
@@ -88,11 +81,11 @@ class TestConnectionPoolTimedStack < Minitest::Test
|
|
88
81
|
end
|
89
82
|
|
90
83
|
def test_pop_wait
|
91
|
-
thread = Thread.start
|
84
|
+
thread = Thread.start {
|
92
85
|
@stack.pop
|
93
|
-
|
86
|
+
}
|
94
87
|
|
95
|
-
Thread.pass while thread.status ==
|
88
|
+
Thread.pass while thread.status == "run"
|
96
89
|
|
97
90
|
object = Object.new
|
98
91
|
|
@@ -102,7 +95,7 @@ class TestConnectionPoolTimedStack < Minitest::Test
|
|
102
95
|
end
|
103
96
|
|
104
97
|
def test_pop_shutdown
|
105
|
-
@stack.shutdown {
|
98
|
+
@stack.shutdown {}
|
106
99
|
|
107
100
|
assert_raises ConnectionPool::PoolShuttingDownError do
|
108
101
|
@stack.pop
|
@@ -144,6 +137,4 @@ class TestConnectionPoolTimedStack < Minitest::Test
|
|
144
137
|
refute_empty called
|
145
138
|
assert_empty @stack
|
146
139
|
end
|
147
|
-
|
148
140
|
end
|
149
|
-
|
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.2.
|
4
|
+
version: 2.2.3
|
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: 2020-06-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -70,9 +70,9 @@ files:
|
|
70
70
|
- Rakefile
|
71
71
|
- connection_pool.gemspec
|
72
72
|
- lib/connection_pool.rb
|
73
|
-
- lib/connection_pool/monotonic_time.rb
|
74
73
|
- lib/connection_pool/timed_stack.rb
|
75
74
|
- lib/connection_pool/version.rb
|
75
|
+
- lib/connection_pool/wrapper.rb
|
76
76
|
- test/helper.rb
|
77
77
|
- test/test_connection_pool.rb
|
78
78
|
- test/test_connection_pool_timed_stack.rb
|
@@ -95,8 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
requirements: []
|
98
|
-
|
99
|
-
rubygems_version: 2.6.13
|
98
|
+
rubygems_version: 3.1.2
|
100
99
|
signing_key:
|
101
100
|
specification_version: 4
|
102
101
|
summary: Generic connection pool for Ruby
|
@@ -1,66 +0,0 @@
|
|
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
|