connection_pool 2.5.4 → 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 +20 -0
- data/README.md +44 -50
- data/connection_pool.gemspec +11 -3
- data/lib/connection_pool/timed_stack.rb +43 -34
- data/lib/connection_pool/version.rb +1 -1
- data/lib/connection_pool/wrapper.rb +15 -28
- data/lib/connection_pool.rb +25 -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,25 @@
|
|
|
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
|
+
|
|
17
|
+
2.5.5
|
|
18
|
+
------
|
|
19
|
+
|
|
20
|
+
- Support `ConnectionPool::TimedStack#pop(exception: false)` [#207]
|
|
21
|
+
to avoid using exceptions as control flow.
|
|
22
|
+
|
|
3
23
|
2.5.4
|
|
4
24
|
------
|
|
5
25
|
|
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
|
|
@@ -54,26 +54,31 @@ class ConnectionPool::TimedStack
|
|
|
54
54
|
# immediately returned. If no connection is available within the given
|
|
55
55
|
# timeout a ConnectionPool::TimeoutError is raised.
|
|
56
56
|
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
+
# Other options may be used by subclasses that extend TimedStack.
|
|
62
|
+
def pop(timeout: 0.5, exception: ConnectionPool::TimeoutError, **)
|
|
64
63
|
deadline = current_time + timeout
|
|
65
64
|
@mutex.synchronize do
|
|
66
65
|
loop do
|
|
67
66
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
68
|
-
if (conn = try_fetch_connection(
|
|
67
|
+
if (conn = try_fetch_connection(**))
|
|
69
68
|
return conn
|
|
70
69
|
end
|
|
71
70
|
|
|
72
|
-
connection = try_create(
|
|
71
|
+
connection = try_create(**)
|
|
73
72
|
return connection if connection
|
|
74
73
|
|
|
75
74
|
to_wait = deadline - current_time
|
|
76
|
-
|
|
75
|
+
if to_wait <= 0
|
|
76
|
+
if exception
|
|
77
|
+
raise exception, "Waited #{timeout} sec, #{length}/#{@max} available"
|
|
78
|
+
else
|
|
79
|
+
return nil
|
|
80
|
+
end
|
|
81
|
+
end
|
|
77
82
|
@resource.wait(@mutex, to_wait)
|
|
78
83
|
end
|
|
79
84
|
end
|
|
@@ -98,21 +103,20 @@ class ConnectionPool::TimedStack
|
|
|
98
103
|
|
|
99
104
|
##
|
|
100
105
|
# Reaps connections that were checked in more than +idle_seconds+ ago.
|
|
101
|
-
def reap(idle_seconds
|
|
102
|
-
raise ArgumentError, "reap must receive a block" unless
|
|
106
|
+
def reap(idle_seconds:)
|
|
107
|
+
raise ArgumentError, "reap must receive a block" unless block_given?
|
|
103
108
|
raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
|
|
104
109
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
105
110
|
|
|
106
|
-
idle
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
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
|
|
113
117
|
break unless conn
|
|
114
118
|
|
|
115
|
-
|
|
119
|
+
yield conn
|
|
116
120
|
end
|
|
117
121
|
end
|
|
118
122
|
|
|
@@ -152,15 +156,15 @@ class ConnectionPool::TimedStack
|
|
|
152
156
|
# This method must returns a connection from the stack if one exists. Allows
|
|
153
157
|
# subclasses with expensive match/search algorithms to avoid double-handling
|
|
154
158
|
# their stack.
|
|
155
|
-
def try_fetch_connection(
|
|
156
|
-
connection_stored?(
|
|
159
|
+
def try_fetch_connection(**)
|
|
160
|
+
connection_stored?(**) && fetch_connection(**)
|
|
157
161
|
end
|
|
158
162
|
|
|
159
163
|
##
|
|
160
164
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
161
165
|
#
|
|
162
166
|
# This method must returns true if a connection is available on the stack.
|
|
163
|
-
def connection_stored?(
|
|
167
|
+
def connection_stored?(**)
|
|
164
168
|
!@que.empty?
|
|
165
169
|
end
|
|
166
170
|
|
|
@@ -168,7 +172,7 @@ class ConnectionPool::TimedStack
|
|
|
168
172
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
169
173
|
#
|
|
170
174
|
# This method must return a connection from the stack.
|
|
171
|
-
def fetch_connection(
|
|
175
|
+
def fetch_connection(**)
|
|
172
176
|
@que.pop&.first
|
|
173
177
|
end
|
|
174
178
|
|
|
@@ -176,8 +180,8 @@ class ConnectionPool::TimedStack
|
|
|
176
180
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
177
181
|
#
|
|
178
182
|
# This method must shut down all connections on the stack.
|
|
179
|
-
def shutdown_connections(
|
|
180
|
-
while (conn = try_fetch_connection(
|
|
183
|
+
def shutdown_connections(**)
|
|
184
|
+
while (conn = try_fetch_connection(**))
|
|
181
185
|
@created -= 1 unless @created == 0
|
|
182
186
|
@shutdown_block.call(conn)
|
|
183
187
|
end
|
|
@@ -193,6 +197,8 @@ class ConnectionPool::TimedStack
|
|
|
193
197
|
|
|
194
198
|
@created -= 1 unless @created == 0
|
|
195
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`.
|
|
196
202
|
@que.shift.first
|
|
197
203
|
end
|
|
198
204
|
|
|
@@ -201,14 +207,17 @@ class ConnectionPool::TimedStack
|
|
|
201
207
|
#
|
|
202
208
|
# Returns true if the first connection in the stack has been idle for more than idle_seconds
|
|
203
209
|
def idle_connections?(idle_seconds)
|
|
204
|
-
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
|
|
205
214
|
end
|
|
206
215
|
|
|
207
216
|
##
|
|
208
217
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
209
218
|
#
|
|
210
219
|
# This method must return +obj+ to the stack.
|
|
211
|
-
def store_connection(obj,
|
|
220
|
+
def store_connection(obj, **)
|
|
212
221
|
@que.push [obj, current_time]
|
|
213
222
|
end
|
|
214
223
|
|
|
@@ -217,7 +226,7 @@ class ConnectionPool::TimedStack
|
|
|
217
226
|
#
|
|
218
227
|
# This method must create a connection if and only if the total number of
|
|
219
228
|
# connections allowed has not been met.
|
|
220
|
-
def try_create(
|
|
229
|
+
def try_create(**)
|
|
221
230
|
unless @created == @max
|
|
222
231
|
object = @create_block.call
|
|
223
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,73 +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(**)
|
|
61
|
+
# We need to manage exception handling manually here in order
|
|
62
|
+
# to work correctly with `Timeout.timeout` and `Thread#raise`.
|
|
63
|
+
# Otherwise an interrupted Thread can leak connections.
|
|
107
64
|
Thread.handle_interrupt(Exception => :never) do
|
|
108
|
-
conn = checkout(
|
|
65
|
+
conn = checkout(**)
|
|
109
66
|
begin
|
|
110
67
|
Thread.handle_interrupt(Exception => :immediate) do
|
|
111
68
|
yield conn
|
|
@@ -151,13 +108,15 @@ class ConnectionPool
|
|
|
151
108
|
::Thread.current[@discard_key] = block || proc { |conn| conn }
|
|
152
109
|
end
|
|
153
110
|
|
|
154
|
-
def checkout(
|
|
111
|
+
def checkout(timeout: @timeout, **)
|
|
155
112
|
if ::Thread.current[@key]
|
|
156
113
|
::Thread.current[@key_count] += 1
|
|
157
114
|
::Thread.current[@key]
|
|
158
115
|
else
|
|
116
|
+
conn = @available.pop(timeout:, **)
|
|
117
|
+
::Thread.current[@key] = conn
|
|
159
118
|
::Thread.current[@key_count] = 1
|
|
160
|
-
|
|
119
|
+
conn
|
|
161
120
|
end
|
|
162
121
|
end
|
|
163
122
|
|
|
@@ -192,29 +151,24 @@ class ConnectionPool
|
|
|
192
151
|
# Shuts down the ConnectionPool by passing each connection to +block+ and
|
|
193
152
|
# then removing it from the pool. Attempting to checkout a connection after
|
|
194
153
|
# shutdown will raise +ConnectionPool::PoolShuttingDownError+.
|
|
195
|
-
def shutdown(&
|
|
196
|
-
@available.shutdown(&
|
|
154
|
+
def shutdown(&)
|
|
155
|
+
@available.shutdown(&)
|
|
197
156
|
end
|
|
198
157
|
|
|
199
158
|
##
|
|
200
159
|
# Reloads the ConnectionPool by passing each connection to +block+ and then
|
|
201
160
|
# removing it the pool. Subsequent checkouts will create new connections as
|
|
202
161
|
# needed.
|
|
203
|
-
def reload(&
|
|
204
|
-
@available.shutdown(reload: true, &
|
|
162
|
+
def reload(&)
|
|
163
|
+
@available.shutdown(reload: true, &)
|
|
205
164
|
end
|
|
206
165
|
|
|
207
166
|
## Reaps idle connections that have been idle for over +idle_seconds+.
|
|
208
167
|
# +idle_seconds+ defaults to 60.
|
|
209
|
-
def reap(idle_seconds
|
|
210
|
-
@available.reap(idle_seconds
|
|
168
|
+
def reap(idle_seconds: 60, &)
|
|
169
|
+
@available.reap(idle_seconds:, &)
|
|
211
170
|
end
|
|
212
171
|
|
|
213
|
-
# Size of this connection pool
|
|
214
|
-
attr_reader :size
|
|
215
|
-
# Automatically drop all connections after fork
|
|
216
|
-
attr_reader :auto_reload_after_fork
|
|
217
|
-
|
|
218
172
|
# Number of pool entries available for checkout at this instant.
|
|
219
173
|
def available
|
|
220
174
|
@available.length
|
|
@@ -228,3 +182,4 @@ end
|
|
|
228
182
|
|
|
229
183
|
require_relative "connection_pool/timed_stack"
|
|
230
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
|
- - ">="
|