Skip to content

Commit 55f3913

Browse files
authored
Add the ability to discard the current connection (#204)
1 parent d641937 commit 55f3913

4 files changed

Lines changed: 104 additions & 1 deletion

File tree

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,25 @@ Thread.new do
129129
end
130130
```
131131

132+
## Discarding Connections
133+
134+
You can discard connections in the ConnectionPool instance to remove connections that are broken and can't be restarted.
135+
136+
NOTE: the connection is not closed. It will just be removed from the pool so it won't be selected again.
137+
138+
It can only be done inside the block passed to `with` or `with_timeout`.
139+
140+
```ruby
141+
pool.with do |conn|
142+
begin
143+
conn.execute("SELECT 1")
144+
rescue SomeConnectionError
145+
pool.discard_current_connection # remove the connection from the pool
146+
raise
147+
end
148+
end
149+
```
150+
132151
## Current State
133152

134153
There are several methods that return information about a pool.

lib/connection_pool.rb

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ def initialize(options = {}, &block)
9999
@available = TimedStack.new(@size, &block)
100100
@key = :"pool-#{@available.object_id}"
101101
@key_count = :"pool-#{@available.object_id}-count"
102+
@discard_key = :"pool-#{@available.object_id}-discard"
102103
INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES
103104
end
104105

@@ -116,6 +117,32 @@ def with(options = {})
116117
end
117118
alias_method :then, :with
118119

120+
##
121+
# Marks the current thread's checked-out connection for discard.
122+
#
123+
# When a connection is marked for discard, it will not be returned to the pool
124+
# when checked in. Instead, the connection will be discarded.
125+
# This is useful when a connection has become invalid or corrupted
126+
# and should not be reused.
127+
#
128+
# Note: This only affects the connection currently checked out by the calling thread.
129+
# The connection will be discarded when +checkin+ is called.
130+
#
131+
# @return [void]
132+
#
133+
# @example
134+
# pool.with do |conn|
135+
# begin
136+
# conn.execute("SELECT 1")
137+
# rescue SomeConnectionError
138+
# pool.discard_current_connection # Mark connection as bad
139+
# raise
140+
# end
141+
# end
142+
def discard_current_connection
143+
::Thread.current[@discard_key] = true
144+
end
145+
119146
def checkout(options = {})
120147
if ::Thread.current[@key]
121148
::Thread.current[@key_count] += 1
@@ -129,7 +156,12 @@ def checkout(options = {})
129156
def checkin(force: false)
130157
if ::Thread.current[@key]
131158
if ::Thread.current[@key_count] == 1 || force
132-
@available.push(::Thread.current[@key])
159+
if ::Thread.current[@discard_key]
160+
@available.decrement_created
161+
::Thread.current[@discard_key] = nil
162+
else
163+
@available.push(::Thread.current[@key])
164+
end
133165
::Thread.current[@key] = nil
134166
::Thread.current[@key_count] = nil
135167
else

lib/connection_pool/timed_stack.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ def idle
134134
@que.length
135135
end
136136

137+
##
138+
# Reduce the created count
139+
def decrement_created
140+
@created -= 1 unless @created == 0
141+
end
142+
137143
private
138144

139145
def current_time

test/test_connection_pool.rb

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ def test_explicit_return
186186
def mock.disconnect!
187187
raise "should not disconnect upon explicit return"
188188
end
189+
189190
mock
190191
}
191192

@@ -246,6 +247,24 @@ def test_checkin
246247
assert_same conn, Thread.new { pool.checkout }.value
247248
end
248249

250+
def test_discard
251+
pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
252+
pool.checkout
253+
254+
Thread.new {
255+
assert_raises Timeout::Error do
256+
pool.checkout
257+
end
258+
}.join
259+
260+
pool.discard_current_connection
261+
pool.checkin
262+
263+
assert_equal 1, pool.size
264+
assert_equal 0, pool.idle
265+
assert_equal 1, pool.available
266+
end
267+
249268
def test_returns_value
250269
pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
251270
assert_equal 1, pool.with { |o| 1 }
@@ -410,6 +429,33 @@ def test_nested_checkout
410429
assert_equal ["inner", "outer", "other"], recorder.calls
411430
end
412431

432+
def test_nested_discard
433+
recorder = Recorder.new
434+
pool = ConnectionPool.new(size: 1) { {recorder: recorder} }
435+
pool.with do |r_outer|
436+
@other = Thread.new { |t|
437+
pool.with do |r_other|
438+
r_other[:recorder].do_work("other")
439+
end
440+
}
441+
442+
pool.with do |r_inner|
443+
@inner = r_inner
444+
r_inner[:recorder].do_work("inner")
445+
pool.discard_current_connection
446+
end
447+
448+
Thread.pass
449+
450+
r_outer[:recorder].do_work("outer")
451+
end
452+
453+
@other.join
454+
455+
assert_equal ["inner", "outer", "other"], recorder.calls
456+
refute_same @inner, pool.checkout
457+
end
458+
413459
def test_shutdown_is_executed_for_all_connections
414460
recorders = []
415461

0 commit comments

Comments
 (0)