connection_pool 2.5.5 → 3.0.0
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/Changes.md +14 -0
- data/README.md +44 -50
- data/connection_pool.gemspec +11 -3
- data/lib/connection_pool/timed_stack.rb +33 -34
- data/lib/connection_pool/version.rb +1 -1
- data/lib/connection_pool/wrapper.rb +15 -28
- data/lib/connection_pool.rb +22 -70
- metadata +9 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60f53f4a83f6a13be41059753aeff5fad1518a45946aa13ab29111f1612c5119
|
|
4
|
+
data.tar.gz: 7edefa375ea7f3852eee9d7644f2aae955119e0cd96049697b7f38bfc54e5c87
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3007eb8a2cf804da6b3850162098b221a5a613244ba20506fed109e3a47e0046099ba56c48ccd8a260a3afc13362552ddd9040a512ca1b512cfae1f18937c32b
|
|
7
|
+
data.tar.gz: 4cdb681918cefe624b3c00e6d7580330f120c6c53cc414e694d6568eee7f24ed109d69ec150c7a7ccbf5dec17ffb1cfdc8422e78bcb9d1b436240bb81c5464a6
|
data/Changes.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# connection_pool Changelog
|
|
2
2
|
|
|
3
|
+
3.0.0
|
|
4
|
+
------
|
|
5
|
+
|
|
6
|
+
- **BREAKING CHANGES** `ConnectionPool` and `ConnectionPool::TimedStack` now
|
|
7
|
+
use keyword arguments rather than positional arguments everywhere. Expected impact is minimal as most people use the `with` API, which is unchanged.
|
|
8
|
+
```ruby
|
|
9
|
+
pool = ConnectionPool.new(size: 5, timeout: 5)
|
|
10
|
+
pool.checkout(1) # 2.x
|
|
11
|
+
pool.reap(30) # 2.x
|
|
12
|
+
pool.checkout(timeout: 1) # 3.x
|
|
13
|
+
pool.reap(idle_seconds: 30) # 3.x
|
|
14
|
+
```
|
|
15
|
+
- Dropped support for Ruby <3.2.0
|
|
16
|
+
|
|
3
17
|
2.5.5
|
|
4
18
|
------
|
|
5
19
|
|
data/README.md
CHANGED
|
@@ -38,7 +38,7 @@ connection pool and a raw client.
|
|
|
38
38
|
$redis.then { |r| r.set 'foo' 'bar' }
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Optionally, you can specify a timeout override
|
|
41
|
+
Optionally, you can specify a timeout override:
|
|
42
42
|
|
|
43
43
|
``` ruby
|
|
44
44
|
$memcached.with(timeout: 2.0) do |conn|
|
|
@@ -46,11 +46,9 @@ $memcached.with(timeout: 2.0) do |conn|
|
|
|
46
46
|
end
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
This will only modify the
|
|
50
|
-
invocation.
|
|
49
|
+
This will only modify the timeout for this particular invocation.
|
|
51
50
|
This is useful if you want to fail-fast on certain non-critical
|
|
52
51
|
sections when a resource is not available, or conversely if you are comfortable blocking longer on a particular resource.
|
|
53
|
-
This is not implemented in the `ConnectionPool::Wrapper` class.
|
|
54
52
|
|
|
55
53
|
## Migrating to a Connection Pool
|
|
56
54
|
|
|
@@ -72,7 +70,7 @@ $redis.with do |conn|
|
|
|
72
70
|
end
|
|
73
71
|
```
|
|
74
72
|
|
|
75
|
-
Once you've ported your entire system to use `with`, you can
|
|
73
|
+
Once you've ported your entire system to use `with`, you can remove `::Wrapper` and use `ConnectionPool` directly.
|
|
76
74
|
|
|
77
75
|
|
|
78
76
|
## Shutdown
|
|
@@ -90,64 +88,52 @@ Shutting down a connection pool will block until all connections are checked in
|
|
|
90
88
|
|
|
91
89
|
## Reload
|
|
92
90
|
|
|
93
|
-
You can reload a ConnectionPool instance
|
|
94
|
-
|
|
91
|
+
You can reload a ConnectionPool instance if it is necessary to close all existing connections and continue to use the pool.
|
|
92
|
+
ConnectionPool will automatically reload if the process is forked.
|
|
93
|
+
Use `auto_reload_after_fork: false` if you don't want this behavior.
|
|
95
94
|
|
|
96
95
|
```ruby
|
|
97
|
-
cp = ConnectionPool.new { Redis.new }
|
|
98
|
-
cp.reload { |conn| conn.quit }
|
|
96
|
+
cp = ConnectionPool.new(auto_reload_after_fork: false) { Redis.new }
|
|
97
|
+
cp.reload { |conn| conn.quit } # reload manually
|
|
99
98
|
cp.with { |conn| conn.get('some-count') }
|
|
100
99
|
```
|
|
101
100
|
|
|
102
|
-
Like `shutdown`,
|
|
101
|
+
Like `shutdown`, `reload` will block until all connections are checked in and closed.
|
|
103
102
|
|
|
104
103
|
## Reap
|
|
105
104
|
|
|
106
|
-
You can reap
|
|
105
|
+
You can call `reap` periodically on the ConnectionPool instance to close connections that were created but have not been used for a certain amount of time. This can be useful in environments where connections are expensive.
|
|
107
106
|
|
|
108
|
-
You can specify how many seconds the connections have to be idle for them to be reaped.
|
|
109
|
-
Defaults to 60 seconds.
|
|
107
|
+
You can specify how many seconds the connections have to be idle for them to be reaped, defaulting to 60 seconds.
|
|
110
108
|
|
|
111
109
|
```ruby
|
|
112
110
|
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
111
|
|
|
118
|
-
|
|
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).
|
|
112
|
+
# Start a reaper thread to reap connections that have been
|
|
113
|
+
# idle more than 300 seconds (5 minutes)
|
|
124
114
|
Thread.new do
|
|
125
115
|
loop do
|
|
126
|
-
cp.reap(300
|
|
127
|
-
sleep
|
|
116
|
+
cp.reap(idle_seconds: 300, &:close)
|
|
117
|
+
sleep 30
|
|
128
118
|
end
|
|
129
119
|
end
|
|
130
120
|
```
|
|
131
121
|
|
|
132
122
|
## Discarding Connections
|
|
133
123
|
|
|
134
|
-
You can discard connections in the ConnectionPool instance to remove connections that are broken and can't be
|
|
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
|
-
|
|
124
|
+
You can discard connections in the ConnectionPool instance to remove connections that are broken and can't be repaired.
|
|
125
|
+
It can only be done inside the block passed to `with`.
|
|
140
126
|
Takes an optional block that will be executed with the connection.
|
|
141
127
|
|
|
142
128
|
```ruby
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
pool.with do |conn|
|
|
130
|
+
begin
|
|
131
|
+
conn.execute("SELECT 1")
|
|
132
|
+
rescue SomeConnectionError
|
|
133
|
+
pool.discard_current_connection(&:close) # remove the connection from the pool
|
|
134
|
+
raise
|
|
135
|
+
end
|
|
136
|
+
end
|
|
151
137
|
```
|
|
152
138
|
|
|
153
139
|
## Current State
|
|
@@ -169,20 +155,28 @@ end
|
|
|
169
155
|
cp.idle # => 1
|
|
170
156
|
```
|
|
171
157
|
|
|
172
|
-
|
|
173
|
-
|
|
158
|
+
## Upgrading from ConnectionPool 2
|
|
159
|
+
|
|
160
|
+
* Support for Ruby <3.2 has been removed.
|
|
161
|
+
* ConnectionPool's APIs now consistently use keyword arguments everywhere.
|
|
162
|
+
Positional arguments must be converted to keywords:
|
|
163
|
+
```ruby
|
|
164
|
+
pool = ConnectionPool.new(size: 5, timeout: 5)
|
|
165
|
+
pool.checkout(1) # 2.x
|
|
166
|
+
pool.reap(30) # 2.x
|
|
167
|
+
pool.checkout(timeout: 1) # 3.x
|
|
168
|
+
pool.reap(idle_seconds: 30) # 3.x
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Notes
|
|
174
172
|
|
|
175
173
|
- Connections are lazily created as needed.
|
|
176
|
-
-
|
|
177
|
-
connections should be self-repairing. This is true of the Dalli and Redis
|
|
178
|
-
clients.
|
|
179
|
-
- **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
|
|
174
|
+
- **WARNING**: Avoid `Timeout.timeout` in your Ruby code or you can see
|
|
180
175
|
occasional silent corruption and mysterious errors. The Timeout API is unsafe
|
|
181
|
-
and
|
|
182
|
-
|
|
176
|
+
and dangerous to use. Use proper socket timeout options as exposed by
|
|
177
|
+
Net::HTTP, Redis, Dalli, etc.
|
|
183
178
|
|
|
184
179
|
|
|
185
|
-
Author
|
|
186
|
-
------
|
|
180
|
+
## Author
|
|
187
181
|
|
|
188
|
-
Mike Perham, [@getajobmike](https://
|
|
182
|
+
Mike Perham, [@getajobmike](https://ruby.social/@getajobmike), <https://www.mikeperham.com>
|
data/connection_pool.gemspec
CHANGED
|
@@ -15,10 +15,18 @@ Gem::Specification.new do |s|
|
|
|
15
15
|
s.executables = []
|
|
16
16
|
s.require_paths = ["lib"]
|
|
17
17
|
s.license = "MIT"
|
|
18
|
+
|
|
19
|
+
s.required_ruby_version = ">= 3.2.0"
|
|
18
20
|
s.add_development_dependency "bundler"
|
|
19
|
-
s.add_development_dependency "
|
|
21
|
+
s.add_development_dependency "maxitest"
|
|
20
22
|
s.add_development_dependency "rake"
|
|
21
|
-
s.required_ruby_version = ">= 2.5.0"
|
|
22
23
|
|
|
23
|
-
s.metadata = {
|
|
24
|
+
s.metadata = {
|
|
25
|
+
"bug_tracker_uri" => "https://github.com/mperham/connection_pool/issues",
|
|
26
|
+
"documentation_uri" => "https://github.com/mperham/connection_pool/wiki",
|
|
27
|
+
"changelog_uri" => "https://github.com/mperham/connection_pool/blob/main/Changes.md",
|
|
28
|
+
"source_code_uri" => "https://github.com/mperham/connection_pool",
|
|
29
|
+
"homepage_uri" => "https://github.com/mperham/connection_pool",
|
|
30
|
+
"rubygems_mfa_required" => "true"
|
|
31
|
+
}
|
|
24
32
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
#
|
|
6
6
|
# Examples:
|
|
7
7
|
#
|
|
8
|
-
# ts = TimedStack.new(1) { MyConnection.new }
|
|
8
|
+
# ts = TimedStack.new(size: 1) { MyConnection.new }
|
|
9
9
|
#
|
|
10
10
|
# # fetch a connection
|
|
11
11
|
# conn = ts.pop
|
|
@@ -22,7 +22,7 @@ class ConnectionPool::TimedStack
|
|
|
22
22
|
##
|
|
23
23
|
# Creates a new pool with +size+ connections that are created from the given
|
|
24
24
|
# +block+.
|
|
25
|
-
def initialize(size
|
|
25
|
+
def initialize(size: 0, &block)
|
|
26
26
|
@create_block = block
|
|
27
27
|
@created = 0
|
|
28
28
|
@que = []
|
|
@@ -33,15 +33,15 @@ class ConnectionPool::TimedStack
|
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
##
|
|
36
|
-
# Returns +obj+ to the stack.
|
|
36
|
+
# Returns +obj+ to the stack. Additional kwargs are ignored in TimedStack but may be
|
|
37
37
|
# used by subclasses that extend TimedStack.
|
|
38
|
-
def push(obj,
|
|
38
|
+
def push(obj, **)
|
|
39
39
|
@mutex.synchronize do
|
|
40
40
|
if @shutdown_block
|
|
41
41
|
@created -= 1 unless @created == 0
|
|
42
42
|
@shutdown_block.call(obj)
|
|
43
43
|
else
|
|
44
|
-
store_connection obj,
|
|
44
|
+
store_connection obj, **
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
@resource.broadcast
|
|
@@ -58,28 +58,23 @@ class ConnectionPool::TimedStack
|
|
|
58
58
|
# @option options [Class] :exception (ConnectionPool::TimeoutError) Exception class to raise
|
|
59
59
|
# if an entry was not available within the timeout period. Use `exception: false` to return nil.
|
|
60
60
|
#
|
|
61
|
-
# The +timeout+ argument will be removed in 3.0.
|
|
62
61
|
# Other options may be used by subclasses that extend TimedStack.
|
|
63
|
-
def pop(timeout
|
|
64
|
-
options, timeout = timeout, 0.5 if Hash === timeout
|
|
65
|
-
timeout = options.fetch :timeout, timeout
|
|
66
|
-
|
|
62
|
+
def pop(timeout: 0.5, exception: ConnectionPool::TimeoutError, **)
|
|
67
63
|
deadline = current_time + timeout
|
|
68
64
|
@mutex.synchronize do
|
|
69
65
|
loop do
|
|
70
66
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
71
|
-
if (conn = try_fetch_connection(
|
|
67
|
+
if (conn = try_fetch_connection(**))
|
|
72
68
|
return conn
|
|
73
69
|
end
|
|
74
70
|
|
|
75
|
-
connection = try_create(
|
|
71
|
+
connection = try_create(**)
|
|
76
72
|
return connection if connection
|
|
77
73
|
|
|
78
74
|
to_wait = deadline - current_time
|
|
79
75
|
if to_wait <= 0
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
raise ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available"
|
|
76
|
+
if exception
|
|
77
|
+
raise exception, "Waited #{timeout} sec, #{length}/#{@max} available"
|
|
83
78
|
else
|
|
84
79
|
return nil
|
|
85
80
|
end
|
|
@@ -108,21 +103,20 @@ class ConnectionPool::TimedStack
|
|
|
108
103
|
|
|
109
104
|
##
|
|
110
105
|
# Reaps connections that were checked in more than +idle_seconds+ ago.
|
|
111
|
-
def reap(idle_seconds
|
|
112
|
-
raise ArgumentError, "reap must receive a block" unless
|
|
106
|
+
def reap(idle_seconds:)
|
|
107
|
+
raise ArgumentError, "reap must receive a block" unless block_given?
|
|
113
108
|
raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
|
|
114
109
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
115
110
|
|
|
116
|
-
idle
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
end
|
|
111
|
+
count = idle
|
|
112
|
+
count.times do
|
|
113
|
+
conn = @mutex.synchronize do
|
|
114
|
+
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
115
|
+
reserve_idle_connection(idle_seconds)
|
|
116
|
+
end
|
|
123
117
|
break unless conn
|
|
124
118
|
|
|
125
|
-
|
|
119
|
+
yield conn
|
|
126
120
|
end
|
|
127
121
|
end
|
|
128
122
|
|
|
@@ -162,15 +156,15 @@ class ConnectionPool::TimedStack
|
|
|
162
156
|
# This method must returns a connection from the stack if one exists. Allows
|
|
163
157
|
# subclasses with expensive match/search algorithms to avoid double-handling
|
|
164
158
|
# their stack.
|
|
165
|
-
def try_fetch_connection(
|
|
166
|
-
connection_stored?(
|
|
159
|
+
def try_fetch_connection(**)
|
|
160
|
+
connection_stored?(**) && fetch_connection(**)
|
|
167
161
|
end
|
|
168
162
|
|
|
169
163
|
##
|
|
170
164
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
171
165
|
#
|
|
172
166
|
# This method must returns true if a connection is available on the stack.
|
|
173
|
-
def connection_stored?(
|
|
167
|
+
def connection_stored?(**)
|
|
174
168
|
!@que.empty?
|
|
175
169
|
end
|
|
176
170
|
|
|
@@ -178,7 +172,7 @@ class ConnectionPool::TimedStack
|
|
|
178
172
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
179
173
|
#
|
|
180
174
|
# This method must return a connection from the stack.
|
|
181
|
-
def fetch_connection(
|
|
175
|
+
def fetch_connection(**)
|
|
182
176
|
@que.pop&.first
|
|
183
177
|
end
|
|
184
178
|
|
|
@@ -186,8 +180,8 @@ class ConnectionPool::TimedStack
|
|
|
186
180
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
187
181
|
#
|
|
188
182
|
# This method must shut down all connections on the stack.
|
|
189
|
-
def shutdown_connections(
|
|
190
|
-
while (conn = try_fetch_connection(
|
|
183
|
+
def shutdown_connections(**)
|
|
184
|
+
while (conn = try_fetch_connection(**))
|
|
191
185
|
@created -= 1 unless @created == 0
|
|
192
186
|
@shutdown_block.call(conn)
|
|
193
187
|
end
|
|
@@ -203,6 +197,8 @@ class ConnectionPool::TimedStack
|
|
|
203
197
|
|
|
204
198
|
@created -= 1 unless @created == 0
|
|
205
199
|
|
|
200
|
+
# Most active elements are at the tail of the array.
|
|
201
|
+
# Most idle will be at the head so `shift` rather than `pop`.
|
|
206
202
|
@que.shift.first
|
|
207
203
|
end
|
|
208
204
|
|
|
@@ -211,14 +207,17 @@ class ConnectionPool::TimedStack
|
|
|
211
207
|
#
|
|
212
208
|
# Returns true if the first connection in the stack has been idle for more than idle_seconds
|
|
213
209
|
def idle_connections?(idle_seconds)
|
|
214
|
-
connection_stored?
|
|
210
|
+
return unless connection_stored?
|
|
211
|
+
# Most idle will be at the head so `first`
|
|
212
|
+
age = (current_time - @que.first.last)
|
|
213
|
+
age > idle_seconds
|
|
215
214
|
end
|
|
216
215
|
|
|
217
216
|
##
|
|
218
217
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
219
218
|
#
|
|
220
219
|
# This method must return +obj+ to the stack.
|
|
221
|
-
def store_connection(obj,
|
|
220
|
+
def store_connection(obj, **)
|
|
222
221
|
@que.push [obj, current_time]
|
|
223
222
|
end
|
|
224
223
|
|
|
@@ -227,7 +226,7 @@ class ConnectionPool::TimedStack
|
|
|
227
226
|
#
|
|
228
227
|
# This method must create a connection if and only if the total number of
|
|
229
228
|
# connections allowed has not been met.
|
|
230
|
-
def try_create(
|
|
229
|
+
def try_create(**)
|
|
231
230
|
unless @created == @max
|
|
232
231
|
object = @create_block.call
|
|
233
232
|
@created += 1
|
|
@@ -2,20 +2,20 @@ class ConnectionPool
|
|
|
2
2
|
class Wrapper < ::BasicObject
|
|
3
3
|
METHODS = [:with, :pool_shutdown, :wrapped_pool]
|
|
4
4
|
|
|
5
|
-
def initialize(options
|
|
6
|
-
@pool = options.fetch(:pool) { ::ConnectionPool.new(options, &
|
|
5
|
+
def initialize(**options, &)
|
|
6
|
+
@pool = options.fetch(:pool) { ::ConnectionPool.new(**options, &) }
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def wrapped_pool
|
|
10
10
|
@pool
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def with(&
|
|
14
|
-
@pool.with(&
|
|
13
|
+
def with(**, &)
|
|
14
|
+
@pool.with(**, &)
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
def pool_shutdown(&
|
|
18
|
-
@pool.shutdown(&
|
|
17
|
+
def pool_shutdown(&)
|
|
18
|
+
@pool.shutdown(&)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def pool_size
|
|
@@ -26,31 +26,18 @@ class ConnectionPool
|
|
|
26
26
|
@pool.available
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def respond_to?(id,
|
|
30
|
-
METHODS.include?(id) || with { |c| c.respond_to?(id,
|
|
29
|
+
def respond_to?(id, *, **)
|
|
30
|
+
METHODS.include?(id) || with { |c| c.respond_to?(id, *, **) }
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
elsif ::RUBY_VERSION >= "2.7.0"
|
|
41
|
-
ruby2_keywords def method_missing(name, *args, &block)
|
|
42
|
-
with do |connection|
|
|
43
|
-
connection.send(name, *args, &block)
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
else
|
|
47
|
-
def method_missing(name, *args, &block)
|
|
48
|
-
with do |connection|
|
|
49
|
-
connection.send(name, *args, &block)
|
|
50
|
-
end
|
|
33
|
+
def respond_to_missing?(id, *, **)
|
|
34
|
+
with { |c| c.respond_to?(id, *, **) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def method_missing(name, *, **, &)
|
|
38
|
+
with do |connection|
|
|
39
|
+
connection.send(name, *, **, &)
|
|
51
40
|
end
|
|
52
41
|
end
|
|
53
|
-
# rubocop:enable Style/MethodMissingSuper
|
|
54
|
-
# rubocop:enable Style/MissingRespondToMissing
|
|
55
42
|
end
|
|
56
43
|
end
|
data/lib/connection_pool.rb
CHANGED
|
@@ -39,76 +39,30 @@ end
|
|
|
39
39
|
# - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true
|
|
40
40
|
#
|
|
41
41
|
class ConnectionPool
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def self.wrap(options, &block)
|
|
45
|
-
Wrapper.new(options, &block)
|
|
42
|
+
def self.wrap(**, &)
|
|
43
|
+
Wrapper.new(**, &)
|
|
46
44
|
end
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
INSTANCES = ObjectSpace::WeakMap.new
|
|
50
|
-
private_constant :INSTANCES
|
|
51
|
-
|
|
52
|
-
def self.after_fork
|
|
53
|
-
INSTANCES.values.each do |pool|
|
|
54
|
-
next unless pool.auto_reload_after_fork
|
|
55
|
-
|
|
56
|
-
# We're on after fork, so we know all other threads are dead.
|
|
57
|
-
# All we need to do is to ensure the main thread doesn't have a
|
|
58
|
-
# checked out connection
|
|
59
|
-
pool.checkin(force: true)
|
|
60
|
-
pool.reload do |connection|
|
|
61
|
-
# Unfortunately we don't know what method to call to close the connection,
|
|
62
|
-
# so we try the most common one.
|
|
63
|
-
connection.close if connection.respond_to?(:close)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
nil
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
if ::Process.respond_to?(:_fork) # MRI 3.1+
|
|
70
|
-
module ForkTracker
|
|
71
|
-
def _fork
|
|
72
|
-
pid = super
|
|
73
|
-
if pid == 0
|
|
74
|
-
ConnectionPool.after_fork
|
|
75
|
-
end
|
|
76
|
-
pid
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
Process.singleton_class.prepend(ForkTracker)
|
|
80
|
-
end
|
|
81
|
-
else
|
|
82
|
-
INSTANCES = nil
|
|
83
|
-
private_constant :INSTANCES
|
|
84
|
-
|
|
85
|
-
def self.after_fork
|
|
86
|
-
# noop
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def initialize(options = {}, &block)
|
|
91
|
-
raise ArgumentError, "Connection pool requires a block" unless block
|
|
92
|
-
|
|
93
|
-
options = DEFAULTS.merge(options)
|
|
46
|
+
attr_reader :size
|
|
94
47
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@auto_reload_after_fork = options.fetch(:auto_reload_after_fork)
|
|
48
|
+
def initialize(timeout: 5, size: 5, auto_reload_after_fork: true, &)
|
|
49
|
+
raise ArgumentError, "Connection pool requires a block" unless block_given?
|
|
98
50
|
|
|
99
|
-
@
|
|
51
|
+
@size = Integer(size)
|
|
52
|
+
@timeout = Float(timeout)
|
|
53
|
+
@available = TimedStack.new(size: @size, &)
|
|
100
54
|
@key = :"pool-#{@available.object_id}"
|
|
101
55
|
@key_count = :"pool-#{@available.object_id}-count"
|
|
102
56
|
@discard_key = :"pool-#{@available.object_id}-discard"
|
|
103
|
-
INSTANCES[self] = self if
|
|
57
|
+
INSTANCES[self] = self if auto_reload_after_fork && INSTANCES
|
|
104
58
|
end
|
|
105
59
|
|
|
106
|
-
def with(
|
|
60
|
+
def with(**)
|
|
107
61
|
# We need to manage exception handling manually here in order
|
|
108
62
|
# to work correctly with `Timeout.timeout` and `Thread#raise`.
|
|
109
63
|
# Otherwise an interrupted Thread can leak connections.
|
|
110
64
|
Thread.handle_interrupt(Exception => :never) do
|
|
111
|
-
conn = checkout(
|
|
65
|
+
conn = checkout(**)
|
|
112
66
|
begin
|
|
113
67
|
Thread.handle_interrupt(Exception => :immediate) do
|
|
114
68
|
yield conn
|
|
@@ -154,13 +108,15 @@ class ConnectionPool
|
|
|
154
108
|
::Thread.current[@discard_key] = block || proc { |conn| conn }
|
|
155
109
|
end
|
|
156
110
|
|
|
157
|
-
def checkout(
|
|
111
|
+
def checkout(timeout: @timeout, **)
|
|
158
112
|
if ::Thread.current[@key]
|
|
159
113
|
::Thread.current[@key_count] += 1
|
|
160
114
|
::Thread.current[@key]
|
|
161
115
|
else
|
|
116
|
+
conn = @available.pop(timeout:, **)
|
|
117
|
+
::Thread.current[@key] = conn
|
|
162
118
|
::Thread.current[@key_count] = 1
|
|
163
|
-
|
|
119
|
+
conn
|
|
164
120
|
end
|
|
165
121
|
end
|
|
166
122
|
|
|
@@ -195,29 +151,24 @@ class ConnectionPool
|
|
|
195
151
|
# Shuts down the ConnectionPool by passing each connection to +block+ and
|
|
196
152
|
# then removing it from the pool. Attempting to checkout a connection after
|
|
197
153
|
# shutdown will raise +ConnectionPool::PoolShuttingDownError+.
|
|
198
|
-
def shutdown(&
|
|
199
|
-
@available.shutdown(&
|
|
154
|
+
def shutdown(&)
|
|
155
|
+
@available.shutdown(&)
|
|
200
156
|
end
|
|
201
157
|
|
|
202
158
|
##
|
|
203
159
|
# Reloads the ConnectionPool by passing each connection to +block+ and then
|
|
204
160
|
# removing it the pool. Subsequent checkouts will create new connections as
|
|
205
161
|
# needed.
|
|
206
|
-
def reload(&
|
|
207
|
-
@available.shutdown(reload: true, &
|
|
162
|
+
def reload(&)
|
|
163
|
+
@available.shutdown(reload: true, &)
|
|
208
164
|
end
|
|
209
165
|
|
|
210
166
|
## Reaps idle connections that have been idle for over +idle_seconds+.
|
|
211
167
|
# +idle_seconds+ defaults to 60.
|
|
212
|
-
def reap(idle_seconds
|
|
213
|
-
@available.reap(idle_seconds
|
|
168
|
+
def reap(idle_seconds: 60, &)
|
|
169
|
+
@available.reap(idle_seconds:, &)
|
|
214
170
|
end
|
|
215
171
|
|
|
216
|
-
# Size of this connection pool
|
|
217
|
-
attr_reader :size
|
|
218
|
-
# Automatically drop all connections after fork
|
|
219
|
-
attr_reader :auto_reload_after_fork
|
|
220
|
-
|
|
221
172
|
# Number of pool entries available for checkout at this instant.
|
|
222
173
|
def available
|
|
223
174
|
@available.length
|
|
@@ -231,3 +182,4 @@ end
|
|
|
231
182
|
|
|
232
183
|
require_relative "connection_pool/timed_stack"
|
|
233
184
|
require_relative "connection_pool/wrapper"
|
|
185
|
+
require_relative "connection_pool/fork"
|
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:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mike Perham
|
|
@@ -25,19 +25,19 @@ dependencies:
|
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '0'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: maxitest
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
33
|
+
version: '0'
|
|
34
34
|
type: :development
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
40
|
+
version: '0'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: rake
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,7 +72,11 @@ homepage: https://github.com/mperham/connection_pool
|
|
|
72
72
|
licenses:
|
|
73
73
|
- MIT
|
|
74
74
|
metadata:
|
|
75
|
+
bug_tracker_uri: https://github.com/mperham/connection_pool/issues
|
|
76
|
+
documentation_uri: https://github.com/mperham/connection_pool/wiki
|
|
75
77
|
changelog_uri: https://github.com/mperham/connection_pool/blob/main/Changes.md
|
|
78
|
+
source_code_uri: https://github.com/mperham/connection_pool
|
|
79
|
+
homepage_uri: https://github.com/mperham/connection_pool
|
|
76
80
|
rubygems_mfa_required: 'true'
|
|
77
81
|
rdoc_options: []
|
|
78
82
|
require_paths:
|
|
@@ -81,7 +85,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
81
85
|
requirements:
|
|
82
86
|
- - ">="
|
|
83
87
|
- !ruby/object:Gem::Version
|
|
84
|
-
version: 2.
|
|
88
|
+
version: 3.2.0
|
|
85
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
90
|
requirements:
|
|
87
91
|
- - ">="
|