connection_pool 2.5.0 → 2.5.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 299b38ab20df15319b32d1a947b291137a5ab0f569d6ebf7153402f525f978c3
4
- data.tar.gz: 300e5f15434761a80e388f5ff3a97e23d0a9d87d373e10703417231d6e14cf50
3
+ metadata.gz: 24c74a1caa5e04827c47155b75e19805bcda4c329e28793309dfa95b5881c4bd
4
+ data.tar.gz: 23aadf2da494be8c3314039700606cd4e01b58d6cae1f45e3b7cdef57a98e7bd
5
5
  SHA512:
6
- metadata.gz: 1ecdda6209d316a78ce8509514b4cb9676f5adae3cd07c95c75c9564b77a427d3c417e502aa32af65374f6aa63f8a5808b9ddf42d39ff89e9522adaf4c59dcd0
7
- data.tar.gz: b1fb7ab8bc2cbcae36371268b03cdb59c4e25855cacd0d9588bfa012aea84801d4d879dff0a311492e89ba2584626029b85c1c3c9cf7efd51f8efb482c983f94
6
+ metadata.gz: 0f2385ddf4619ebc1bf9e2f202024b330c170ae2e4e7e63480be0f556242140af75eb3d272f6d262b2fc859625f0861c560af6a58c20bea3d69b005e2d248472
7
+ data.tar.gz: 0ac5103c69aa2e8226d2fa872714f9bd611b24ea171e91ce3d7a3c9be62b9887e24916cf4b6b1e5bc8c407349f87c3ea855c4659dbf5f3f137cd45738301d1a6
data/Changes.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # connection_pool Changelog
2
2
 
3
+ 2.5.5
4
+ ------
5
+
6
+ - Support `ConnectionPool::TimedStack#pop(exception: false)` [#207]
7
+ to avoid using exceptions as control flow.
8
+
9
+ 2.5.4
10
+ ------
11
+
12
+ - Add ability to remove a broken connection from the pool [#204, womblep]
13
+
14
+ 2.5.3
15
+ ------
16
+
17
+ - Fix TruffleRuby/JRuby crash [#201]
18
+
19
+ 2.5.2
20
+ ------
21
+
22
+ - Rollback inadvertant change to `auto_reload_after_fork` default. [#200]
23
+
24
+ 2.5.1
25
+ ------
26
+
27
+ - Pass options to TimedStack in `checkout` [#195]
28
+ - Optimize connection lookup [#196]
29
+ - Fixes for use with Ractors
30
+
3
31
  2.5.0
4
32
  ------
5
33
 
data/README.md CHANGED
@@ -129,6 +129,27 @@ Thread.new do
129
129
  end
130
130
  ```
131
131
 
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
+ Takes an optional block that will be executed with the connection.
141
+
142
+ ```ruby
143
+ pool.with do |conn|
144
+ begin
145
+ conn.execute("SELECT 1")
146
+ rescue SomeConnectionError
147
+ pool.discard_current_connection # remove the connection from the pool
148
+ raise
149
+ end
150
+ end
151
+ ```
152
+
132
153
  ## Current State
133
154
 
134
155
  There are several methods that return information about a pool.
@@ -1,8 +1,8 @@
1
1
  ##
2
2
  # The TimedStack manages a pool of homogeneous connections (or any resource
3
- # you wish to manage). Connections are created lazily up to a given maximum
3
+ # you wish to manage). Connections are created lazily up to a given maximum
4
4
  # number.
5
-
5
+ #
6
6
  # Examples:
7
7
  #
8
8
  # ts = TimedStack.new(1) { MyConnection.new }
@@ -16,14 +16,12 @@
16
16
  # conn = ts.pop
17
17
  # ts.pop timeout: 5
18
18
  # #=> raises ConnectionPool::TimeoutError after 5 seconds
19
-
20
19
  class ConnectionPool::TimedStack
21
20
  attr_reader :max
22
21
 
23
22
  ##
24
23
  # Creates a new pool with +size+ connections that are created from the given
25
24
  # +block+.
26
-
27
25
  def initialize(size = 0, &block)
28
26
  @create_block = block
29
27
  @created = 0
@@ -35,9 +33,8 @@ class ConnectionPool::TimedStack
35
33
  end
36
34
 
37
35
  ##
38
- # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be
36
+ # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be
39
37
  # used by subclasses that extend TimedStack.
40
-
41
38
  def push(obj, options = {})
42
39
  @mutex.synchronize do
43
40
  if @shutdown_block
@@ -53,14 +50,16 @@ class ConnectionPool::TimedStack
53
50
  alias_method :<<, :push
54
51
 
55
52
  ##
56
- # Retrieves a connection from the stack. If a connection is available it is
57
- # immediately returned. If no connection is available within the given
53
+ # Retrieves a connection from the stack. If a connection is available it is
54
+ # immediately returned. If no connection is available within the given
58
55
  # timeout a ConnectionPool::TimeoutError is raised.
59
56
  #
60
- # +:timeout+ is the only checked entry in +options+ and is preferred over
61
- # the +timeout+ argument (which will be removed in a future release). Other
62
- # options may be used by subclasses that extend TimedStack.
63
-
57
+ # @option options [Float] :timeout (0.5) Wait this many seconds for an available entry
58
+ # @option options [Class] :exception (ConnectionPool::TimeoutError) Exception class to raise
59
+ # if an entry was not available within the timeout period. Use `exception: false` to return nil.
60
+ #
61
+ # The +timeout+ argument will be removed in 3.0.
62
+ # Other options may be used by subclasses that extend TimedStack.
64
63
  def pop(timeout = 0.5, options = {})
65
64
  options, timeout = timeout, 0.5 if Hash === timeout
66
65
  timeout = options.fetch :timeout, timeout
@@ -69,13 +68,22 @@ class ConnectionPool::TimedStack
69
68
  @mutex.synchronize do
70
69
  loop do
71
70
  raise ConnectionPool::PoolShuttingDownError if @shutdown_block
72
- return fetch_connection(options) if connection_stored?(options)
71
+ if (conn = try_fetch_connection(options))
72
+ return conn
73
+ end
73
74
 
74
75
  connection = try_create(options)
75
76
  return connection if connection
76
77
 
77
78
  to_wait = deadline - current_time
78
- raise ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0
79
+ if to_wait <= 0
80
+ exc = options.fetch(:exception, ConnectionPool::TimeoutError)
81
+ if exc
82
+ raise ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available"
83
+ else
84
+ return nil
85
+ end
86
+ end
79
87
  @resource.wait(@mutex, to_wait)
80
88
  end
81
89
  end
@@ -86,7 +94,6 @@ class ConnectionPool::TimedStack
86
94
  # removing it from the pool. Attempting to checkout a connection after
87
95
  # shutdown will raise +ConnectionPool::PoolShuttingDownError+ unless
88
96
  # +:reload+ is +true+.
89
-
90
97
  def shutdown(reload: false, &block)
91
98
  raise ArgumentError, "shutdown must receive a block" unless block
92
99
 
@@ -121,14 +128,12 @@ class ConnectionPool::TimedStack
121
128
 
122
129
  ##
123
130
  # Returns +true+ if there are no available connections.
124
-
125
131
  def empty?
126
132
  (@created - @que.length) >= @max
127
133
  end
128
134
 
129
135
  ##
130
136
  # The number of connections available on the stack.
131
-
132
137
  def length
133
138
  @max - @created + @que.length
134
139
  end
@@ -139,6 +144,12 @@ class ConnectionPool::TimedStack
139
144
  @que.length
140
145
  end
141
146
 
147
+ ##
148
+ # Reduce the created count
149
+ def decrement_created
150
+ @created -= 1 unless @created == 0
151
+ end
152
+
142
153
  private
143
154
 
144
155
  def current_time
@@ -148,8 +159,17 @@ class ConnectionPool::TimedStack
148
159
  ##
149
160
  # This is an extension point for TimedStack and is called with a mutex.
150
161
  #
151
- # This method must returns true if a connection is available on the stack.
162
+ # This method must returns a connection from the stack if one exists. Allows
163
+ # subclasses with expensive match/search algorithms to avoid double-handling
164
+ # their stack.
165
+ def try_fetch_connection(options = nil)
166
+ connection_stored?(options) && fetch_connection(options)
167
+ end
152
168
 
169
+ ##
170
+ # This is an extension point for TimedStack and is called with a mutex.
171
+ #
172
+ # This method must returns true if a connection is available on the stack.
153
173
  def connection_stored?(options = nil)
154
174
  !@que.empty?
155
175
  end
@@ -158,7 +178,6 @@ class ConnectionPool::TimedStack
158
178
  # This is an extension point for TimedStack and is called with a mutex.
159
179
  #
160
180
  # This method must return a connection from the stack.
161
-
162
181
  def fetch_connection(options = nil)
163
182
  @que.pop&.first
164
183
  end
@@ -167,10 +186,8 @@ class ConnectionPool::TimedStack
167
186
  # This is an extension point for TimedStack and is called with a mutex.
168
187
  #
169
188
  # This method must shut down all connections on the stack.
170
-
171
189
  def shutdown_connections(options = nil)
172
- while connection_stored?(options)
173
- conn = fetch_connection(options)
190
+ while (conn = try_fetch_connection(options))
174
191
  @created -= 1 unless @created == 0
175
192
  @shutdown_block.call(conn)
176
193
  end
@@ -181,7 +198,6 @@ class ConnectionPool::TimedStack
181
198
  #
182
199
  # This method returns the oldest idle connection if it has been idle for more than idle_seconds.
183
200
  # This requires that the stack is kept in order of checked in time (oldest first).
184
-
185
201
  def reserve_idle_connection(idle_seconds)
186
202
  return unless idle_connections?(idle_seconds)
187
203
 
@@ -194,7 +210,6 @@ class ConnectionPool::TimedStack
194
210
  # This is an extension point for TimedStack and is called with a mutex.
195
211
  #
196
212
  # Returns true if the first connection in the stack has been idle for more than idle_seconds
197
-
198
213
  def idle_connections?(idle_seconds)
199
214
  connection_stored? && (current_time - @que.first.last > idle_seconds)
200
215
  end
@@ -203,7 +218,6 @@ class ConnectionPool::TimedStack
203
218
  # This is an extension point for TimedStack and is called with a mutex.
204
219
  #
205
220
  # This method must return +obj+ to the stack.
206
-
207
221
  def store_connection(obj, options = nil)
208
222
  @que.push [obj, current_time]
209
223
  end
@@ -213,7 +227,6 @@ class ConnectionPool::TimedStack
213
227
  #
214
228
  # This method must create a connection if and only if the total number of
215
229
  # connections allowed has not been met.
216
-
217
230
  def try_create(options = nil)
218
231
  unless @created == @max
219
232
  object = @create_block.call
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.5.0"
2
+ VERSION = "2.5.5"
3
3
  end
@@ -39,7 +39,7 @@ end
39
39
  # - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true
40
40
  #
41
41
  class ConnectionPool
42
- DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}
42
+ DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}.freeze
43
43
 
44
44
  def self.wrap(options, &block)
45
45
  Wrapper.new(options, &block)
@@ -99,10 +99,14 @@ class ConnectionPool
99
99
  @available = TimedStack.new(@size, &block)
100
100
  @key = :"pool-#{@available.object_id}"
101
101
  @key_count = :"pool-#{@available.object_id}-count"
102
- INSTANCES[self] = self if INSTANCES
102
+ @discard_key = :"pool-#{@available.object_id}-discard"
103
+ INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES
103
104
  end
104
105
 
105
106
  def with(options = {})
107
+ # We need to manage exception handling manually here in order
108
+ # to work correctly with `Timeout.timeout` and `Thread#raise`.
109
+ # Otherwise an interrupted Thread can leak connections.
106
110
  Thread.handle_interrupt(Exception => :never) do
107
111
  conn = checkout(options)
108
112
  begin
@@ -116,20 +120,65 @@ class ConnectionPool
116
120
  end
117
121
  alias_method :then, :with
118
122
 
123
+ ##
124
+ # Marks the current thread's checked-out connection for discard.
125
+ #
126
+ # When a connection is marked for discard, it will not be returned to the pool
127
+ # when checked in. Instead, the connection will be discarded.
128
+ # This is useful when a connection has become invalid or corrupted
129
+ # and should not be reused.
130
+ #
131
+ # Takes an optional block that will be called with the connection to be discarded.
132
+ # The block should perform any necessary clean-up on the connection.
133
+ #
134
+ # @yield [conn]
135
+ # @yieldparam conn [Object] The connection to be discarded.
136
+ # @yieldreturn [void]
137
+ #
138
+ #
139
+ # Note: This only affects the connection currently checked out by the calling thread.
140
+ # The connection will be discarded when +checkin+ is called.
141
+ #
142
+ # @return [void]
143
+ #
144
+ # @example
145
+ # pool.with do |conn|
146
+ # begin
147
+ # conn.execute("SELECT 1")
148
+ # rescue SomeConnectionError
149
+ # pool.discard_current_connection # Mark connection as bad
150
+ # raise
151
+ # end
152
+ # end
153
+ def discard_current_connection(&block)
154
+ ::Thread.current[@discard_key] = block || proc { |conn| conn }
155
+ end
156
+
119
157
  def checkout(options = {})
120
158
  if ::Thread.current[@key]
121
159
  ::Thread.current[@key_count] += 1
122
160
  ::Thread.current[@key]
123
161
  else
124
162
  ::Thread.current[@key_count] = 1
125
- ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
163
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options)
126
164
  end
127
165
  end
128
166
 
129
167
  def checkin(force: false)
130
168
  if ::Thread.current[@key]
131
169
  if ::Thread.current[@key_count] == 1 || force
132
- @available.push(::Thread.current[@key])
170
+ if ::Thread.current[@discard_key]
171
+ begin
172
+ @available.decrement_created
173
+ ::Thread.current[@discard_key].call(::Thread.current[@key])
174
+ rescue
175
+ nil
176
+ ensure
177
+ ::Thread.current[@discard_key] = nil
178
+ end
179
+ else
180
+ @available.push(::Thread.current[@key])
181
+ end
133
182
  ::Thread.current[@key] = nil
134
183
  ::Thread.current[@key_count] = nil
135
184
  else
@@ -146,7 +195,6 @@ class ConnectionPool
146
195
  # Shuts down the ConnectionPool by passing each connection to +block+ and
147
196
  # then removing it from the pool. Attempting to checkout a connection after
148
197
  # shutdown will raise +ConnectionPool::PoolShuttingDownError+.
149
-
150
198
  def shutdown(&block)
151
199
  @available.shutdown(&block)
152
200
  end
@@ -155,7 +203,6 @@ class ConnectionPool
155
203
  # Reloads the ConnectionPool by passing each connection to +block+ and then
156
204
  # removing it the pool. Subsequent checkouts will create new connections as
157
205
  # needed.
158
-
159
206
  def reload(&block)
160
207
  @available.shutdown(reload: true, &block)
161
208
  end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: connection_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.0
4
+ version: 2.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  - Damian Janowski
9
- autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2025-01-07 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
@@ -75,7 +74,6 @@ licenses:
75
74
  metadata:
76
75
  changelog_uri: https://github.com/mperham/connection_pool/blob/main/Changes.md
77
76
  rubygems_mfa_required: 'true'
78
- post_install_message:
79
77
  rdoc_options: []
80
78
  require_paths:
81
79
  - lib
@@ -90,8 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
88
  - !ruby/object:Gem::Version
91
89
  version: '0'
92
90
  requirements: []
93
- rubygems_version: 3.5.22
94
- signing_key:
91
+ rubygems_version: 3.6.9
95
92
  specification_version: 4
96
93
  summary: Generic connection pool for Ruby
97
94
  test_files: []