connection_pool 2.4.1 → 2.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea0776fcb09a3cc48ef4ca03774399e20b09e51039d0c47c1e4cb3bac621c52b
4
- data.tar.gz: b955d6b4e984259f20ae8cf6414f59692f9a51848424231363643e0c16dd2a3f
3
+ metadata.gz: 0a46ac6651f50f053c03abb5ab7bdc0a6e3ddde7c1f8dd1d76fbd7939f3ba31d
4
+ data.tar.gz: 71cd4da429af569a8a8a9d4a2af51081e5363eb184f29b876a3c4a3ccf3e689e
5
5
  SHA512:
6
- metadata.gz: bf57d8b5547502d91f5550ca6ea0be16905604c90e61efb6741e5ec3ce607c7a65f0b31e1673c96c60a06a2f64f5239cab6e94d3a50095fb822ea9b1c1bb2f0a
7
- data.tar.gz: 4b42aa5aa67b0e45bbbc8a9f29ca3a969efd8ade3b6dfca6cff082f526ec65a2a2e5c8fa17f512d33470b82535af8b675fc80903ccd75db26748e9845dd9a612
6
+ metadata.gz: e73f6dea5b92fd29d8249011390f3f1bff7562d0c189602c88d8d95fa5337fa50270f6ed8a8c2031423db2a15d22685668032f3e6e0629c04d99b71342b28a50
7
+ data.tar.gz: 010ce12b06b7f678eae90c949366c94450a03c3b0f8f676ec35acff8b71470eb66c29b6ec3838981bf070136e72f9698687251284f7d6ac1d7f46cd406727073
data/Changes.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # connection_pool Changelog
2
2
 
3
+ 2.5.1
4
+ ------
5
+
6
+ - Pass options to TimedStack in `checkout` [#195]
7
+ - Optimize connection lookup [#196]
8
+ - Fixes for use with Ractors
9
+
10
+ 2.5.0
11
+ ------
12
+
13
+ - Reap idle connections [#187]
14
+ ```ruby
15
+ idle_timeout = 60
16
+ pool = ConnectionPool.new ...
17
+ pool.reap(idle_timeout, &:close)
18
+ ```
19
+ - `ConnectionPool#idle` returns the count of connections not in use [#187]
20
+
3
21
  2.4.1
4
22
  ------
5
23
 
data/README.md CHANGED
@@ -101,6 +101,34 @@ cp.with { |conn| conn.get('some-count') }
101
101
 
102
102
  Like `shutdown`, this will block until all connections are checked in and closed.
103
103
 
104
+ ## Reap
105
+
106
+ You can reap idle connections in the ConnectionPool instance to close connections that were created but have not been used for a certain amount of time. This can be useful to run periodically in a separate thread especially if keeping the connection open is resource intensive.
107
+
108
+ You can specify how many seconds the connections have to be idle for them to be reaped.
109
+ Defaults to 60 seconds.
110
+
111
+ ```ruby
112
+ cp = ConnectionPool.new { Redis.new }
113
+ cp.reap(300) { |conn| conn.close } # Reaps connections that have been idle for 300 seconds (5 minutes).
114
+ ```
115
+
116
+ ### Reaper Thread
117
+
118
+ You can start your own reaper thread to reap idle connections in the ConnectionPool instance on a regular interval.
119
+
120
+ ```ruby
121
+ cp = ConnectionPool.new { Redis.new }
122
+
123
+ # Start a reaper thread to reap connections that have been idle for 300 seconds (5 minutes).
124
+ Thread.new do
125
+ loop do
126
+ cp.reap(300) { |conn| conn.close }
127
+ sleep 300
128
+ end
129
+ end
130
+ ```
131
+
104
132
  ## Current State
105
133
 
106
134
  There are several methods that return information about a pool.
@@ -109,11 +137,15 @@ There are several methods that return information about a pool.
109
137
  cp = ConnectionPool.new(size: 10) { Redis.new }
110
138
  cp.size # => 10
111
139
  cp.available # => 10
140
+ cp.idle # => 0
112
141
 
113
142
  cp.with do |conn|
114
143
  cp.size # => 10
115
144
  cp.available # => 9
145
+ cp.idle # => 0
116
146
  end
147
+
148
+ cp.idle # => 1
117
149
  ```
118
150
 
119
151
  Notes
@@ -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,12 +33,12 @@ 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
41
+ @created -= 1 unless @created == 0
44
42
  @shutdown_block.call(obj)
45
43
  else
46
44
  store_connection obj, options
@@ -52,14 +50,13 @@ class ConnectionPool::TimedStack
52
50
  alias_method :<<, :push
53
51
 
54
52
  ##
55
- # Retrieves a connection from the stack. If a connection is available it is
56
- # 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
57
55
  # timeout a ConnectionPool::TimeoutError is raised.
58
56
  #
59
57
  # +:timeout+ is the only checked entry in +options+ and is preferred over
60
- # the +timeout+ argument (which will be removed in a future release). Other
58
+ # the +timeout+ argument (which will be removed in a future release). Other
61
59
  # options may be used by subclasses that extend TimedStack.
62
-
63
60
  def pop(timeout = 0.5, options = {})
64
61
  options, timeout = timeout, 0.5 if Hash === timeout
65
62
  timeout = options.fetch :timeout, timeout
@@ -68,7 +65,9 @@ class ConnectionPool::TimedStack
68
65
  @mutex.synchronize do
69
66
  loop do
70
67
  raise ConnectionPool::PoolShuttingDownError if @shutdown_block
71
- return fetch_connection(options) if connection_stored?(options)
68
+ if (conn = try_fetch_connection(options))
69
+ return conn
70
+ end
72
71
 
73
72
  connection = try_create(options)
74
73
  return connection if connection
@@ -85,7 +84,6 @@ class ConnectionPool::TimedStack
85
84
  # removing it from the pool. Attempting to checkout a connection after
86
85
  # shutdown will raise +ConnectionPool::PoolShuttingDownError+ unless
87
86
  # +:reload+ is +true+.
88
-
89
87
  def shutdown(reload: false, &block)
90
88
  raise ArgumentError, "shutdown must receive a block" unless block
91
89
 
@@ -99,19 +97,43 @@ class ConnectionPool::TimedStack
99
97
  end
100
98
 
101
99
  ##
102
- # Returns +true+ if there are no available connections.
100
+ # Reaps connections that were checked in more than +idle_seconds+ ago.
101
+ def reap(idle_seconds, &block)
102
+ raise ArgumentError, "reap must receive a block" unless block
103
+ raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
104
+ raise ConnectionPool::PoolShuttingDownError if @shutdown_block
105
+
106
+ idle.times do
107
+ conn =
108
+ @mutex.synchronize do
109
+ raise ConnectionPool::PoolShuttingDownError if @shutdown_block
110
+
111
+ reserve_idle_connection(idle_seconds)
112
+ end
113
+ break unless conn
114
+
115
+ block.call(conn)
116
+ end
117
+ end
103
118
 
119
+ ##
120
+ # Returns +true+ if there are no available connections.
104
121
  def empty?
105
122
  (@created - @que.length) >= @max
106
123
  end
107
124
 
108
125
  ##
109
126
  # The number of connections available on the stack.
110
-
111
127
  def length
112
128
  @max - @created + @que.length
113
129
  end
114
130
 
131
+ ##
132
+ # The number of connections created and available on the stack.
133
+ def idle
134
+ @que.length
135
+ end
136
+
115
137
  private
116
138
 
117
139
  def current_time
@@ -121,8 +143,17 @@ class ConnectionPool::TimedStack
121
143
  ##
122
144
  # This is an extension point for TimedStack and is called with a mutex.
123
145
  #
124
- # This method must returns true if a connection is available on the stack.
146
+ # This method must returns a connection from the stack if one exists. Allows
147
+ # subclasses with expensive match/search algorithms to avoid double-handling
148
+ # their stack.
149
+ def try_fetch_connection(options = nil)
150
+ connection_stored?(options) && fetch_connection(options)
151
+ end
125
152
 
153
+ ##
154
+ # This is an extension point for TimedStack and is called with a mutex.
155
+ #
156
+ # This method must returns true if a connection is available on the stack.
126
157
  def connection_stored?(options = nil)
127
158
  !@que.empty?
128
159
  end
@@ -131,31 +162,48 @@ class ConnectionPool::TimedStack
131
162
  # This is an extension point for TimedStack and is called with a mutex.
132
163
  #
133
164
  # This method must return a connection from the stack.
134
-
135
165
  def fetch_connection(options = nil)
136
- @que.pop
166
+ @que.pop&.first
137
167
  end
138
168
 
139
169
  ##
140
170
  # This is an extension point for TimedStack and is called with a mutex.
141
171
  #
142
172
  # This method must shut down all connections on the stack.
143
-
144
173
  def shutdown_connections(options = nil)
145
- while connection_stored?(options)
146
- conn = fetch_connection(options)
174
+ while (conn = try_fetch_connection(options))
175
+ @created -= 1 unless @created == 0
147
176
  @shutdown_block.call(conn)
148
177
  end
149
- @created = 0
150
178
  end
151
179
 
152
180
  ##
153
181
  # This is an extension point for TimedStack and is called with a mutex.
154
182
  #
155
- # This method must return +obj+ to the stack.
183
+ # This method returns the oldest idle connection if it has been idle for more than idle_seconds.
184
+ # This requires that the stack is kept in order of checked in time (oldest first).
185
+ def reserve_idle_connection(idle_seconds)
186
+ return unless idle_connections?(idle_seconds)
187
+
188
+ @created -= 1 unless @created == 0
156
189
 
190
+ @que.shift.first
191
+ end
192
+
193
+ ##
194
+ # This is an extension point for TimedStack and is called with a mutex.
195
+ #
196
+ # Returns true if the first connection in the stack has been idle for more than idle_seconds
197
+ def idle_connections?(idle_seconds)
198
+ connection_stored? && (current_time - @que.first.last > idle_seconds)
199
+ end
200
+
201
+ ##
202
+ # This is an extension point for TimedStack and is called with a mutex.
203
+ #
204
+ # This method must return +obj+ to the stack.
157
205
  def store_connection(obj, options = nil)
158
- @que.push obj
206
+ @que.push [obj, current_time]
159
207
  end
160
208
 
161
209
  ##
@@ -163,7 +211,6 @@ class ConnectionPool::TimedStack
163
211
  #
164
212
  # This method must create a connection if and only if the total number of
165
213
  # connections allowed has not been met.
166
-
167
214
  def try_create(options = nil)
168
215
  unless @created == @max
169
216
  object = @create_block.call
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.4.1"
2
+ VERSION = "2.5.1"
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: false}.freeze
43
43
 
44
44
  def self.wrap(options, &block)
45
45
  Wrapper.new(options, &block)
@@ -99,7 +99,7 @@ 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
+ INSTANCES[self] = self if @auto_reload_after_fork
103
103
  end
104
104
 
105
105
  def with(options = {})
@@ -122,7 +122,7 @@ class ConnectionPool
122
122
  ::Thread.current[@key]
123
123
  else
124
124
  ::Thread.current[@key_count] = 1
125
- ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
125
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options)
126
126
  end
127
127
  end
128
128
 
@@ -146,7 +146,6 @@ class ConnectionPool
146
146
  # Shuts down the ConnectionPool by passing each connection to +block+ and
147
147
  # then removing it from the pool. Attempting to checkout a connection after
148
148
  # shutdown will raise +ConnectionPool::PoolShuttingDownError+.
149
-
150
149
  def shutdown(&block)
151
150
  @available.shutdown(&block)
152
151
  end
@@ -155,11 +154,16 @@ class ConnectionPool
155
154
  # Reloads the ConnectionPool by passing each connection to +block+ and then
156
155
  # removing it the pool. Subsequent checkouts will create new connections as
157
156
  # needed.
158
-
159
157
  def reload(&block)
160
158
  @available.shutdown(reload: true, &block)
161
159
  end
162
160
 
161
+ ## Reaps idle connections that have been idle for over +idle_seconds+.
162
+ # +idle_seconds+ defaults to 60.
163
+ def reap(idle_seconds = 60, &block)
164
+ @available.reap(idle_seconds, &block)
165
+ end
166
+
163
167
  # Size of this connection pool
164
168
  attr_reader :size
165
169
  # Automatically drop all connections after fork
@@ -169,6 +173,11 @@ class ConnectionPool
169
173
  def available
170
174
  @available.length
171
175
  end
176
+
177
+ # Number of pool entries created and idle in the pool.
178
+ def idle
179
+ @available.idle
180
+ end
172
181
  end
173
182
 
174
183
  require_relative "connection_pool/timed_stack"
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.4.1
4
+ version: 2.5.1
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: 2023-05-19 00:00:00.000000000 Z
11
+ date: 2025-04-16 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.4.7
94
- signing_key:
91
+ rubygems_version: 3.6.2
95
92
  specification_version: 4
96
93
  summary: Generic connection pool for Ruby
97
94
  test_files: []