connection_pool 2.1.3 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec91685df50fe18b79ef2cd2ed2fb038e322ff81
4
- data.tar.gz: 99b88e1aa01811e18c2737e8e062c4726ce54666
3
+ metadata.gz: 57159a2ea25da28e141dc80936a642134fbc9680
4
+ data.tar.gz: 7f534538909e40c117b84090a89b6f4fcd5ca8ee
5
5
  SHA512:
6
- metadata.gz: 53d34f8ca6a7d51ce535a83e2821dd33eb15a623df3022b89775c232876c8fec28a2b33adda24248136014d99f0ebe9f09c1f49e1f0ced76435db26538e907a7
7
- data.tar.gz: 660f22d0f828c789942da1da3d7c4131e008dd383a83c74e59ea03f306a59b966e879d42f9704ffa3b7de6accb6c114cfff982d3343bbe34c5e60ecc8f705ac5
6
+ metadata.gz: bb15e188af8037b01d59ab3728ee010033ab6c71cdd82ed6a8f4f59609c23791d9afdb8a9e31c755079cd1ce7c6e0ca20a7b43d2bd4b142b7ddacbd9b9ad94c3
7
+ data.tar.gz: 69e22e8e329f525fb875c2abe1090a35a63d035bb9337d9bd79604f46f1f1c3dbb92a83d6bf8d899388e8873cf04060c288bfef0e760e14b23a2fa12b1ffdf32
data/Changes.md CHANGED
@@ -1,3 +1,13 @@
1
+ connection\_pool changelog
2
+ ---------------------------
3
+
4
+ 2.2.0
5
+ ------
6
+
7
+ - Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems
8
+ impossible to safely work around the issue. Please never, ever use
9
+ `Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75]
10
+
1
11
  2.1.3
2
12
  ------
3
13
 
data/README.md CHANGED
@@ -8,6 +8,11 @@ MongoDB has its own connection pool. ActiveRecord has its own connection pool.
8
8
  This is a generic connection pool that can be used with anything, e.g. Redis,
9
9
  Dalli and other Ruby network clients.
10
10
 
11
+ **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
12
+ occasional silent corruption and mysterious errors. The Timeout API is unsafe
13
+ and cannot be used correctly, ever. Use proper socket timeout options as
14
+ exposed by Net::HTTP, Redis, Dalli, etc.
15
+
11
16
 
12
17
  Usage
13
18
  -----
@@ -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
  #
@@ -52,26 +53,36 @@ class ConnectionPool
52
53
  @key = :"current-#{@available.object_id}"
53
54
  end
54
55
 
56
+ if Thread.respond_to?(:handle_interrupt)
57
+
58
+ # MRI
59
+ def with(options = {})
60
+ Thread.handle_interrupt(Exception => :never) do
61
+ conn = checkout(options)
62
+ begin
63
+ Thread.handle_interrupt(Exception => :immediate) do
64
+ yield conn
65
+ end
66
+ ensure
67
+ checkin
68
+ end
69
+ end
70
+ end
71
+
72
+ else
73
+
74
+ # jruby 1.7.x
55
75
  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
76
  conn = checkout(options)
61
77
  begin
62
- result = yield conn
63
- success = true # means the connection wasn't interrupted
64
- result
78
+ yield conn
65
79
  ensure
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
80
+ checkin
72
81
  end
73
82
  end
74
83
 
84
+ end
85
+
75
86
  def checkout(options = {})
76
87
  conn = if stack.empty?
77
88
  timeout = options[:timeout] || @timeout
@@ -117,28 +117,6 @@ 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
- # 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
120
  private
143
121
 
144
122
  ##
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.1.3"
2
+ VERSION = "2.2.0"
3
3
  end
@@ -109,38 +109,58 @@ class TestConnectionPool < Minitest::Test
109
109
  assert Thread.new { pool.checkout }.join
110
110
  end
111
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")
112
+ def test_with_timeout
113
+ pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
114
+
115
+ assert_raises Timeout::Error do
116
+ Timeout.timeout(0.01) do
117
+ pool.with do |obj|
118
+ assert_equal 0, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
119
+ sleep 0.015
120
+ end
119
121
  end
120
122
  end
123
+ assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
124
+ end
121
125
 
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
126
+ def test_checkout_ignores_timeout
127
+ skip("Thread.handle_interrupt not available") unless Thread.respond_to?(:handle_interrupt)
127
128
 
128
- checkin_time = 0.05
129
+ pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
130
+ def pool.checkout(options)
131
+ sleep 0.015
132
+ super
133
+ end
129
134
 
135
+ did_something = false
130
136
  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
137
+ Timeout.timeout(0.01) do
138
+ pool.with do |obj|
139
+ did_something = true
140
+ # Timeout::Error will be triggered by any non-trivial Ruby code
141
+ # executed here since it couldn't be raised during checkout.
142
+ # It looks like setting the local variable above does not trigger
143
+ # the Timeout check in MRI 2.2.1.
144
+ obj.tap { obj.hash }
136
145
  end
137
146
  end
138
147
  end
148
+ assert did_something
149
+ assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
150
+ end
139
151
 
140
- GC.start
152
+ def test_explicit_return
153
+ pool = ConnectionPool.new(:timeout => 0, :size => 1) do
154
+ mock = Minitest::Mock.new
155
+ def mock.disconnect!
156
+ raise "should not disconnect upon explicit return"
157
+ end
158
+ mock
159
+ end
141
160
 
142
- # no dangling references to this "connection" remain
143
- assert_equal [], ObjectSpace.each_object(marker_class).to_a
161
+ pool.with do |conn|
162
+ return true
163
+ end
144
164
  end
145
165
 
146
166
  def test_with_timeout_override
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.3
4
+ version: 2.2.0
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: 2015-03-19 00:00:00.000000000 Z
12
+ date: 2015-04-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler