connection_pool 2.1.0 → 2.1.1
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 +4 -4
- data/.travis.yml +10 -6
- data/Changes.md +8 -0
- data/README.md +1 -0
- data/Rakefile +2 -10
- data/connection_pool.gemspec +1 -0
- data/lib/connection_pool.rb +37 -24
- data/lib/connection_pool/timed_stack.rb +16 -0
- data/lib/connection_pool/version.rb +1 -1
- data/test/helper.rb +0 -2
- data/test/test_connection_pool.rb +52 -16
- metadata +17 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c0e26a3abd66776ac9ebe51e38226c1efdbee324
|
4
|
+
data.tar.gz: dfba06ec37dc8fc8a9a5b11e79ca09627e6d439c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 462848c7cb5bcd7be90e1b1b7ea8af8db2b785b45918e73be80e9a901fbe4a3de366ca6bd0c57ef609ef1cfaaa4a2eae6e924b5990b1a716b67db4d2451d3ef7
|
7
|
+
data.tar.gz: 177e4e03153eecb3b04b8bf0d4e98d5f5e9f3be2bb2a74a3464a0901cfc7dfe7679c257d4d180a061debc6cf71dd33d32ba4ad85f21cbf989189ef6bd24e4c02
|
data/.travis.yml
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
---
|
2
|
+
sudo: false
|
3
|
+
cache: bundler
|
2
4
|
language: ruby
|
5
|
+
rvm:
|
6
|
+
- 1.9.3
|
7
|
+
- 2.0.0
|
8
|
+
- 2.1.0
|
9
|
+
- 2.2.0
|
10
|
+
- jruby
|
11
|
+
- rbx-2
|
3
12
|
notifications:
|
4
13
|
email:
|
5
|
-
|
6
|
-
rvm:
|
7
|
-
- 1.9.3
|
8
|
-
- 2.0.0
|
9
|
-
- 2.1.0
|
10
|
-
script: rake test
|
14
|
+
- drbrain@segment7.net
|
data/Changes.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
2.1.1
|
2
|
+
------
|
3
|
+
|
4
|
+
- Work around a subtle race condition with code which uses `Timeout.timeout` and
|
5
|
+
checks out a connection within the timeout block. This might cause
|
6
|
+
connections to get into a bad state and raise very odd errors. [tamird, #67]
|
7
|
+
|
8
|
+
|
1
9
|
2.1.0
|
2
10
|
------
|
3
11
|
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -1,14 +1,6 @@
|
|
1
|
-
|
2
|
-
require 'bundler'
|
3
|
-
Bundler::GemHelper.install_tasks
|
4
|
-
rescue LoadError
|
5
|
-
end
|
1
|
+
require 'bundler/gem_tasks'
|
6
2
|
|
7
3
|
require 'rake/testtask'
|
8
|
-
Rake::TestTask.new
|
9
|
-
test.libs << 'test'
|
10
|
-
test.warning = true
|
11
|
-
test.pattern = 'test/**/test_*.rb'
|
12
|
-
end
|
4
|
+
Rake::TestTask.new
|
13
5
|
|
14
6
|
task :default => :test
|
data/connection_pool.gemspec
CHANGED
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
|
|
15
15
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
16
|
s.require_paths = ["lib"]
|
17
17
|
s.license = "MIT"
|
18
|
+
s.add_development_dependency 'bundler'
|
18
19
|
s.add_development_dependency 'minitest', '>= 5.0.0'
|
19
20
|
s.add_development_dependency 'rake'
|
20
21
|
end
|
data/lib/connection_pool.rb
CHANGED
@@ -2,7 +2,7 @@ require_relative 'connection_pool/version'
|
|
2
2
|
require_relative 'connection_pool/timed_stack'
|
3
3
|
|
4
4
|
# Generic connection pool class for e.g. sharing a limited number of network connections
|
5
|
-
# among many threads. Note: Connections are
|
5
|
+
# among many threads. Note: Connections are lazily created.
|
6
6
|
#
|
7
7
|
# Example usage with block (faster):
|
8
8
|
#
|
@@ -53,22 +53,31 @@ class ConnectionPool
|
|
53
53
|
end
|
54
54
|
|
55
55
|
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
|
56
60
|
conn = checkout(options)
|
57
61
|
begin
|
58
|
-
yield conn
|
62
|
+
result = yield conn
|
63
|
+
success = true # means the connection wasn't interrupted
|
64
|
+
result
|
59
65
|
ensure
|
60
|
-
|
66
|
+
if success
|
67
|
+
# everything is roses, we can safely check the connection back in
|
68
|
+
checkin
|
69
|
+
else
|
70
|
+
@available.discard!(pop_connection)
|
71
|
+
end
|
61
72
|
end
|
62
73
|
end
|
63
74
|
|
64
75
|
def checkout(options = {})
|
65
|
-
|
66
|
-
|
67
|
-
if stack.empty?
|
76
|
+
conn = if stack.empty?
|
68
77
|
timeout = options[:timeout] || @timeout
|
69
|
-
|
78
|
+
@available.pop(timeout: timeout)
|
70
79
|
else
|
71
|
-
|
80
|
+
stack.last
|
72
81
|
end
|
73
82
|
|
74
83
|
stack.push conn
|
@@ -76,14 +85,9 @@ class ConnectionPool
|
|
76
85
|
end
|
77
86
|
|
78
87
|
def checkin
|
79
|
-
|
80
|
-
|
81
|
-
!stack || stack.empty?
|
88
|
+
conn = pop_connection # mutates stack, must be on its own line
|
89
|
+
@available.push(conn) if stack.empty?
|
82
90
|
|
83
|
-
conn = stack.pop
|
84
|
-
if stack.empty?
|
85
|
-
@available << conn
|
86
|
-
end
|
87
91
|
nil
|
88
92
|
end
|
89
93
|
|
@@ -91,6 +95,20 @@ class ConnectionPool
|
|
91
95
|
@available.shutdown(&block)
|
92
96
|
end
|
93
97
|
|
98
|
+
private
|
99
|
+
|
100
|
+
def pop_connection
|
101
|
+
if stack.empty?
|
102
|
+
raise ConnectionPool::Error, 'no connections are checked out'
|
103
|
+
else
|
104
|
+
stack.pop
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def stack
|
109
|
+
::Thread.current[@key] ||= []
|
110
|
+
end
|
111
|
+
|
94
112
|
class Wrapper < ::BasicObject
|
95
113
|
METHODS = [:with, :pool_shutdown]
|
96
114
|
|
@@ -98,13 +116,8 @@ class ConnectionPool
|
|
98
116
|
@pool = ::ConnectionPool.new(options, &block)
|
99
117
|
end
|
100
118
|
|
101
|
-
def with
|
102
|
-
|
103
|
-
begin
|
104
|
-
yield conn
|
105
|
-
ensure
|
106
|
-
@pool.checkin
|
107
|
-
end
|
119
|
+
def with(&block)
|
120
|
+
@pool.with(&block)
|
108
121
|
end
|
109
122
|
|
110
123
|
def pool_shutdown(&block)
|
@@ -112,11 +125,11 @@ class ConnectionPool
|
|
112
125
|
end
|
113
126
|
|
114
127
|
def respond_to?(id, *args)
|
115
|
-
METHODS.include?(id) ||
|
128
|
+
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
116
129
|
end
|
117
130
|
|
118
131
|
def method_missing(name, *args, &block)
|
119
|
-
|
132
|
+
with do |connection|
|
120
133
|
connection.send(name, *args, &block)
|
121
134
|
end
|
122
135
|
end
|
@@ -117,6 +117,22 @@ class ConnectionPool::TimedStack
|
|
117
117
|
@max - @created + @que.length
|
118
118
|
end
|
119
119
|
|
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
|
+
@created -= 1
|
130
|
+
end
|
131
|
+
|
132
|
+
@resource.broadcast
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
120
136
|
private
|
121
137
|
|
122
138
|
##
|
data/test/helper.rb
CHANGED
@@ -47,9 +47,13 @@ class TestConnectionPool < Minitest::Test
|
|
47
47
|
end
|
48
48
|
end.each do |thread|
|
49
49
|
Thread.pass until thread.status == 'sleep'
|
50
|
-
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def kill_threads(threads)
|
54
|
+
threads.each do |thread|
|
51
55
|
thread.kill
|
52
|
-
|
56
|
+
thread.join
|
53
57
|
end
|
54
58
|
end
|
55
59
|
|
@@ -105,6 +109,40 @@ class TestConnectionPool < Minitest::Test
|
|
105
109
|
assert Thread.new { pool.checkout }.join
|
106
110
|
end
|
107
111
|
|
112
|
+
def test_with_with_dangerous_timeouts
|
113
|
+
case RUBY_ENGINE.to_sym
|
114
|
+
when :jruby
|
115
|
+
skip('JRuby GC dislikes this test')
|
116
|
+
when :ruby
|
117
|
+
if RUBY_VERSION == '2.0.0' && RUBY_PATCHLEVEL == 598
|
118
|
+
skip("#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} GC dislikes this test")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
marker_class = Class.new
|
123
|
+
pool = ConnectionPool.new(:timeout => 0, :size => 1) { marker_class.new }
|
124
|
+
|
125
|
+
# no "connections" allocated yet
|
126
|
+
assert_equal [], ObjectSpace.each_object(marker_class).to_a
|
127
|
+
|
128
|
+
checkin_time = 0.05
|
129
|
+
|
130
|
+
assert_raises Timeout::Error do
|
131
|
+
Timeout.timeout(checkin_time) do
|
132
|
+
pool.with do
|
133
|
+
# a "connection" has been allocated
|
134
|
+
refute_equal [], ObjectSpace.each_object(marker_class).to_a
|
135
|
+
sleep 2 * checkin_time
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
GC.start
|
141
|
+
|
142
|
+
# no dangling references to this "connection" remain
|
143
|
+
assert_equal [], ObjectSpace.each_object(marker_class).to_a
|
144
|
+
end
|
145
|
+
|
108
146
|
def test_with_timeout_override
|
109
147
|
pool = ConnectionPool.new(:timeout => 0, :size => 1) { NetworkConnection.new }
|
110
148
|
|
@@ -130,21 +168,13 @@ class TestConnectionPool < Minitest::Test
|
|
130
168
|
pool = ConnectionPool.new(:timeout => 0, :size => 1) { NetworkConnection.new }
|
131
169
|
conn = pool.checkout
|
132
170
|
|
133
|
-
t1 = Thread.new do
|
134
|
-
pool.checkout
|
135
|
-
end
|
136
|
-
|
137
171
|
assert_raises Timeout::Error do
|
138
|
-
|
172
|
+
Thread.new { pool.checkout }.join
|
139
173
|
end
|
140
174
|
|
141
175
|
pool.checkin
|
142
176
|
|
143
|
-
|
144
|
-
pool.checkout
|
145
|
-
end
|
146
|
-
|
147
|
-
assert_same conn, t2.value
|
177
|
+
assert_same conn, Thread.new { pool.checkout }.value
|
148
178
|
end
|
149
179
|
|
150
180
|
def test_returns_value
|
@@ -319,12 +349,14 @@ class TestConnectionPool < Minitest::Test
|
|
319
349
|
Recorder.new.tap { |r| recorders << r }
|
320
350
|
end
|
321
351
|
|
322
|
-
use_pool pool, 3
|
352
|
+
threads = use_pool pool, 3
|
323
353
|
|
324
354
|
pool.shutdown do |recorder|
|
325
355
|
recorder.do_work("shutdown")
|
326
356
|
end
|
327
357
|
|
358
|
+
kill_threads(threads)
|
359
|
+
|
328
360
|
assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
|
329
361
|
end
|
330
362
|
|
@@ -345,7 +377,7 @@ class TestConnectionPool < Minitest::Test
|
|
345
377
|
Recorder.new.tap { |r| recorders << r }
|
346
378
|
end
|
347
379
|
|
348
|
-
use_pool pool,
|
380
|
+
threads = use_pool pool, 2
|
349
381
|
|
350
382
|
pool.checkout
|
351
383
|
|
@@ -353,7 +385,9 @@ class TestConnectionPool < Minitest::Test
|
|
353
385
|
recorder.do_work("shutdown")
|
354
386
|
end
|
355
387
|
|
356
|
-
|
388
|
+
kill_threads(threads)
|
389
|
+
|
390
|
+
assert_equal [["shutdown"], ["shutdown"], []], recorders.map { |r| r.calls }
|
357
391
|
|
358
392
|
pool.checkin
|
359
393
|
|
@@ -375,12 +409,14 @@ class TestConnectionPool < Minitest::Test
|
|
375
409
|
Recorder.new.tap { |r| recorders << r }
|
376
410
|
end
|
377
411
|
|
378
|
-
use_pool wrapper, 3
|
412
|
+
threads = use_pool wrapper, 3
|
379
413
|
|
380
414
|
wrapper.pool_shutdown do |recorder|
|
381
415
|
recorder.do_work("shutdown")
|
382
416
|
end
|
383
417
|
|
418
|
+
kill_threads(threads)
|
419
|
+
|
384
420
|
assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
|
385
421
|
end
|
386
422
|
|
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.1.
|
4
|
+
version: 2.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Perham
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: minitest
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,7 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
95
|
version: '0'
|
82
96
|
requirements: []
|
83
97
|
rubyforge_project:
|
84
|
-
rubygems_version: 2.4.
|
98
|
+
rubygems_version: 2.4.5
|
85
99
|
signing_key:
|
86
100
|
specification_version: 4
|
87
101
|
summary: Generic connection pool for Ruby
|