connection_pool 1.0.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 +7 -0
- data/Changes.md +150 -0
- data/README.md +152 -36
- data/connection_pool.gemspec +18 -11
- data/lib/connection_pool/timed_stack.rb +213 -18
- data/lib/connection_pool/version.rb +1 -1
- data/lib/connection_pool/wrapper.rb +56 -0
- data/lib/connection_pool.rb +179 -47
- metadata +59 -24
- data/.gitignore +0 -4
- data/Gemfile +0 -7
- data/Rakefile +0 -14
- data/test/helper.rb +0 -19
- data/test/test_connection_pool.rb +0 -150
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 24c74a1caa5e04827c47155b75e19805bcda4c329e28793309dfa95b5881c4bd
|
|
4
|
+
data.tar.gz: 23aadf2da494be8c3314039700606cd4e01b58d6cae1f45e3b7cdef57a98e7bd
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0f2385ddf4619ebc1bf9e2f202024b330c170ae2e4e7e63480be0f556242140af75eb3d272f6d262b2fc859625f0861c560af6a58c20bea3d69b005e2d248472
|
|
7
|
+
data.tar.gz: 0ac5103c69aa2e8226d2fa872714f9bd611b24ea171e91ce3d7a3c9be62b9887e24916cf4b6b1e5bc8c407349f87c3ea855c4659dbf5f3f137cd45738301d1a6
|
data/Changes.md
CHANGED
|
@@ -1,3 +1,153 @@
|
|
|
1
|
+
# connection_pool Changelog
|
|
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
|
+
|
|
31
|
+
2.5.0
|
|
32
|
+
------
|
|
33
|
+
|
|
34
|
+
- Reap idle connections [#187]
|
|
35
|
+
```ruby
|
|
36
|
+
idle_timeout = 60
|
|
37
|
+
pool = ConnectionPool.new ...
|
|
38
|
+
pool.reap(idle_timeout, &:close)
|
|
39
|
+
```
|
|
40
|
+
- `ConnectionPool#idle` returns the count of connections not in use [#187]
|
|
41
|
+
|
|
42
|
+
2.4.1
|
|
43
|
+
------
|
|
44
|
+
|
|
45
|
+
- New `auto_reload_after_fork` config option to disable auto-drop [#177, shayonj]
|
|
46
|
+
|
|
47
|
+
2.4.0
|
|
48
|
+
------
|
|
49
|
+
|
|
50
|
+
- Automatically drop all connections after fork [#166]
|
|
51
|
+
|
|
52
|
+
2.3.0
|
|
53
|
+
------
|
|
54
|
+
|
|
55
|
+
- Minimum Ruby version is now 2.5.0
|
|
56
|
+
- Add pool size to TimeoutError message
|
|
57
|
+
|
|
58
|
+
2.2.5
|
|
59
|
+
------
|
|
60
|
+
|
|
61
|
+
- Fix argument forwarding on Ruby 2.7 [#149]
|
|
62
|
+
|
|
63
|
+
2.2.4
|
|
64
|
+
------
|
|
65
|
+
|
|
66
|
+
- Add `reload` to close all connections, recreating them afterwards [Andrew Marshall, #140]
|
|
67
|
+
- Add `then` as a way to use a pool or a bare connection with the same code path [#138]
|
|
68
|
+
|
|
69
|
+
2.2.3
|
|
70
|
+
------
|
|
71
|
+
|
|
72
|
+
- Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130]
|
|
73
|
+
- Use monotonic clock present in all modern Rubies [Tero Tasanen, #109]
|
|
74
|
+
- Remove code hacks necessary for JRuby 1.7
|
|
75
|
+
- Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113]
|
|
76
|
+
|
|
77
|
+
2.2.2
|
|
78
|
+
------
|
|
79
|
+
|
|
80
|
+
- Add pool `size` and `available` accessors for metrics and monitoring
|
|
81
|
+
purposes [#97, robholland]
|
|
82
|
+
|
|
83
|
+
2.2.1
|
|
84
|
+
------
|
|
85
|
+
|
|
86
|
+
- Allow CP::Wrapper to use an existing pool [#87, etiennebarrie]
|
|
87
|
+
- Use monotonic time for more accurate timeouts [#84, jdantonio]
|
|
88
|
+
|
|
89
|
+
2.2.0
|
|
90
|
+
------
|
|
91
|
+
|
|
92
|
+
- Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems
|
|
93
|
+
impossible to safely work around the issue. Please never, ever use
|
|
94
|
+
`Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75]
|
|
95
|
+
|
|
96
|
+
2.1.3
|
|
97
|
+
------
|
|
98
|
+
|
|
99
|
+
- Don't increment created count until connection is successfully
|
|
100
|
+
created. [mylesmegyesi, #73]
|
|
101
|
+
|
|
102
|
+
2.1.2
|
|
103
|
+
------
|
|
104
|
+
|
|
105
|
+
- The connection\_pool will now close any connections which respond to
|
|
106
|
+
`close` (Dalli) or `disconnect!` (Redis). This ensures discarded connections
|
|
107
|
+
from the fix in 2.1.1 are torn down ASAP and don't linger open.
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
2.1.1
|
|
111
|
+
------
|
|
112
|
+
|
|
113
|
+
- Work around a subtle race condition with code which uses `Timeout.timeout` and
|
|
114
|
+
checks out a connection within the timeout block. This might cause
|
|
115
|
+
connections to get into a bad state and raise very odd errors. [tamird, #67]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
2.1.0
|
|
119
|
+
------
|
|
120
|
+
|
|
121
|
+
- Refactoring to better support connection pool subclasses [drbrain,
|
|
122
|
+
#55]
|
|
123
|
+
- `with` should return value of the last expression [#59]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
2.0.0
|
|
127
|
+
-----
|
|
128
|
+
|
|
129
|
+
- The connection pool is now lazy. Connections are created as needed
|
|
130
|
+
and retained until the pool is shut down. [drbrain, #52]
|
|
131
|
+
|
|
132
|
+
1.2.0
|
|
133
|
+
-----
|
|
134
|
+
|
|
135
|
+
- Add `with(options)` and `checkout(options)`. [mattcamuto]
|
|
136
|
+
Allows the caller to override the pool timeout.
|
|
137
|
+
```ruby
|
|
138
|
+
@pool.with(:timeout => 2) do |conn|
|
|
139
|
+
end
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
1.1.0
|
|
143
|
+
-----
|
|
144
|
+
|
|
145
|
+
- New `#shutdown` method (simao)
|
|
146
|
+
|
|
147
|
+
This method accepts a block and calls the block for each
|
|
148
|
+
connection in the pool. After calling this method, trying to get a
|
|
149
|
+
connection from the pool raises `PoolShuttingDownError`.
|
|
150
|
+
|
|
1
151
|
1.0.0
|
|
2
152
|
-----
|
|
3
153
|
|
data/README.md
CHANGED
|
@@ -1,60 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
connection\_pool
|
|
2
|
+
=================
|
|
3
|
+
[](https://github.com/mperham/connection_pool/actions/workflows/ci.yml)
|
|
3
4
|
|
|
4
5
|
Generic connection pooling for Ruby.
|
|
5
6
|
|
|
6
|
-
MongoDB has its own connection pool.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Install
|
|
10
|
-
------------
|
|
11
|
-
|
|
12
|
-
gem install connection_pool
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Notes
|
|
16
|
-
------------
|
|
17
|
-
|
|
18
|
-
- Connections are eager created when the pool is created.
|
|
19
|
-
- There is no provision for repairing or checking the health of a
|
|
20
|
-
connection; connections should be self-repairing. This is
|
|
21
|
-
true of the dalli and redis clients.
|
|
22
|
-
|
|
7
|
+
MongoDB has its own connection pool.
|
|
8
|
+
ActiveRecord has its own connection pool.
|
|
9
|
+
This is a generic connection pool that can be used with anything, e.g. Redis, Dalli and other Ruby network clients.
|
|
23
10
|
|
|
24
11
|
Usage
|
|
25
|
-
|
|
12
|
+
-----
|
|
26
13
|
|
|
27
14
|
Create a pool of objects to share amongst the fibers or threads in your Ruby application:
|
|
28
15
|
|
|
29
16
|
``` ruby
|
|
30
|
-
|
|
17
|
+
$memcached = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new }
|
|
31
18
|
```
|
|
32
19
|
|
|
33
20
|
Then use the pool in your application:
|
|
34
21
|
|
|
35
22
|
``` ruby
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
$memcached.with do |conn|
|
|
24
|
+
conn.get('some-count')
|
|
38
25
|
end
|
|
39
26
|
```
|
|
40
27
|
|
|
41
28
|
If all the objects in the connection pool are in use, `with` will block
|
|
42
|
-
until one becomes available.
|
|
43
|
-
|
|
29
|
+
until one becomes available.
|
|
30
|
+
If no object is available within `:timeout` seconds,
|
|
31
|
+
`with` will raise a `ConnectionPool::TimeoutError` (a subclass of `Timeout::Error`).
|
|
44
32
|
|
|
45
|
-
You can use `ConnectionPool
|
|
46
|
-
|
|
33
|
+
You can also use `ConnectionPool#then` to support _both_ a
|
|
34
|
+
connection pool and a raw client.
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
# Compatible with a raw Redis::Client, and ConnectionPool Redis
|
|
38
|
+
$redis.then { |r| r.set 'foo' 'bar' }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Optionally, you can specify a timeout override using the with-block semantics:
|
|
47
42
|
|
|
48
43
|
``` ruby
|
|
49
|
-
$
|
|
44
|
+
$memcached.with(timeout: 2.0) do |conn|
|
|
45
|
+
conn.get('some-count')
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This will only modify the resource-get timeout for this particular
|
|
50
|
+
invocation.
|
|
51
|
+
This is useful if you want to fail-fast on certain non-critical
|
|
52
|
+
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
|
+
|
|
55
|
+
## Migrating to a Connection Pool
|
|
56
|
+
|
|
57
|
+
You can use `ConnectionPool::Wrapper` to wrap a single global connection, making it easier to migrate existing connection code over time:
|
|
58
|
+
|
|
59
|
+
``` ruby
|
|
60
|
+
$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
|
|
50
61
|
$redis.sadd('foo', 1)
|
|
51
62
|
$redis.smembers('foo')
|
|
52
63
|
```
|
|
53
64
|
|
|
54
|
-
The
|
|
55
|
-
|
|
56
|
-
pool. It's **not** high-performance so you'll want to port your
|
|
57
|
-
performance sensitive code to use `with` as soon as possible.
|
|
65
|
+
The wrapper uses `method_missing` to checkout a connection, run the requested method and then immediately check the connection back into the pool.
|
|
66
|
+
It's **not** high-performance so you'll want to port your performance sensitive code to use `with` as soon as possible.
|
|
58
67
|
|
|
59
68
|
``` ruby
|
|
60
69
|
$redis.with do |conn|
|
|
@@ -63,10 +72,117 @@ $redis.with do |conn|
|
|
|
63
72
|
end
|
|
64
73
|
```
|
|
65
74
|
|
|
66
|
-
Once you've ported your entire system to use `with`, you can simply
|
|
67
|
-
|
|
75
|
+
Once you've ported your entire system to use `with`, you can simply remove `Wrapper` and use the simpler and faster `ConnectionPool`.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## Shutdown
|
|
79
|
+
|
|
80
|
+
You can shut down a ConnectionPool instance once it should no longer be used.
|
|
81
|
+
Further checkout attempts will immediately raise an error but existing checkouts will work.
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
cp = ConnectionPool.new { Redis.new }
|
|
85
|
+
cp.shutdown { |c| c.close }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Shutting down a connection pool will block until all connections are checked in and closed.
|
|
89
|
+
**Note that shutting down is completely optional**; Ruby's garbage collector will reclaim unreferenced pools under normal circumstances.
|
|
90
|
+
|
|
91
|
+
## Reload
|
|
92
|
+
|
|
93
|
+
You can reload a ConnectionPool instance in the case it is desired to close all connections to the pool and, unlike `shutdown`, afterwards recreate connections so the pool may continue to be used.
|
|
94
|
+
Reloading may be useful after forking the process.
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
cp = ConnectionPool.new { Redis.new }
|
|
98
|
+
cp.reload { |conn| conn.quit }
|
|
99
|
+
cp.with { |conn| conn.get('some-count') }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Like `shutdown`, this will block until all connections are checked in and closed.
|
|
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
|
+
|
|
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
|
+
|
|
153
|
+
## Current State
|
|
154
|
+
|
|
155
|
+
There are several methods that return information about a pool.
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
cp = ConnectionPool.new(size: 10) { Redis.new }
|
|
159
|
+
cp.size # => 10
|
|
160
|
+
cp.available # => 10
|
|
161
|
+
cp.idle # => 0
|
|
162
|
+
|
|
163
|
+
cp.with do |conn|
|
|
164
|
+
cp.size # => 10
|
|
165
|
+
cp.available # => 9
|
|
166
|
+
cp.idle # => 0
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
cp.idle # => 1
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Notes
|
|
173
|
+
-----
|
|
174
|
+
|
|
175
|
+
- Connections are lazily created as needed.
|
|
176
|
+
- There is no provision for repairing or checking the health of a connection;
|
|
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
|
|
180
|
+
occasional silent corruption and mysterious errors. The Timeout API is unsafe
|
|
181
|
+
and cannot be used correctly, ever. Use proper socket timeout options as
|
|
182
|
+
exposed by Net::HTTP, Redis, Dalli, etc.
|
|
183
|
+
|
|
68
184
|
|
|
69
185
|
Author
|
|
70
|
-
|
|
186
|
+
------
|
|
71
187
|
|
|
72
|
-
Mike Perham, [@
|
|
188
|
+
Mike Perham, [@getajobmike](https://twitter.com/getajobmike), <https://www.mikeperham.com>
|
data/connection_pool.gemspec
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
require "./lib/connection_pool/version"
|
|
3
2
|
|
|
4
3
|
Gem::Specification.new do |s|
|
|
5
|
-
s.name
|
|
6
|
-
s.version
|
|
7
|
-
s.platform
|
|
8
|
-
s.authors
|
|
9
|
-
s.email
|
|
10
|
-
s.homepage
|
|
11
|
-
s.description = s.summary =
|
|
4
|
+
s.name = "connection_pool"
|
|
5
|
+
s.version = ConnectionPool::VERSION
|
|
6
|
+
s.platform = Gem::Platform::RUBY
|
|
7
|
+
s.authors = ["Mike Perham", "Damian Janowski"]
|
|
8
|
+
s.email = ["mperham@gmail.com", "damian@educabilia.com"]
|
|
9
|
+
s.homepage = "https://github.com/mperham/connection_pool"
|
|
10
|
+
s.description = s.summary = "Generic connection pool for Ruby"
|
|
12
11
|
|
|
13
|
-
s.files
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
s.files = ["Changes.md", "LICENSE", "README.md", "connection_pool.gemspec",
|
|
13
|
+
"lib/connection_pool.rb", "lib/connection_pool/timed_stack.rb",
|
|
14
|
+
"lib/connection_pool/version.rb", "lib/connection_pool/wrapper.rb"]
|
|
15
|
+
s.executables = []
|
|
16
16
|
s.require_paths = ["lib"]
|
|
17
|
+
s.license = "MIT"
|
|
18
|
+
s.add_development_dependency "bundler"
|
|
19
|
+
s.add_development_dependency "minitest", ">= 5.0.0"
|
|
20
|
+
s.add_development_dependency "rake"
|
|
21
|
+
s.required_ruby_version = ">= 2.5.0"
|
|
22
|
+
|
|
23
|
+
s.metadata = {"changelog_uri" => "https://github.com/mperham/connection_pool/blob/main/Changes.md", "rubygems_mfa_required" => "true"}
|
|
17
24
|
end
|
|
@@ -1,42 +1,237 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
##
|
|
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
|
|
4
|
+
# number.
|
|
5
|
+
#
|
|
6
|
+
# Examples:
|
|
7
|
+
#
|
|
8
|
+
# ts = TimedStack.new(1) { MyConnection.new }
|
|
9
|
+
#
|
|
10
|
+
# # fetch a connection
|
|
11
|
+
# conn = ts.pop
|
|
12
|
+
#
|
|
13
|
+
# # return a connection
|
|
14
|
+
# ts.push conn
|
|
15
|
+
#
|
|
16
|
+
# conn = ts.pop
|
|
17
|
+
# ts.pop timeout: 5
|
|
18
|
+
# #=> raises ConnectionPool::TimeoutError after 5 seconds
|
|
4
19
|
class ConnectionPool::TimedStack
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
20
|
+
attr_reader :max
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Creates a new pool with +size+ connections that are created from the given
|
|
24
|
+
# +block+.
|
|
25
|
+
def initialize(size = 0, &block)
|
|
26
|
+
@create_block = block
|
|
27
|
+
@created = 0
|
|
28
|
+
@que = []
|
|
29
|
+
@max = size
|
|
30
|
+
@mutex = Thread::Mutex.new
|
|
31
|
+
@resource = Thread::ConditionVariable.new
|
|
32
|
+
@shutdown_block = nil
|
|
9
33
|
end
|
|
10
34
|
|
|
11
|
-
|
|
35
|
+
##
|
|
36
|
+
# Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be
|
|
37
|
+
# used by subclasses that extend TimedStack.
|
|
38
|
+
def push(obj, options = {})
|
|
12
39
|
@mutex.synchronize do
|
|
13
|
-
@
|
|
40
|
+
if @shutdown_block
|
|
41
|
+
@created -= 1 unless @created == 0
|
|
42
|
+
@shutdown_block.call(obj)
|
|
43
|
+
else
|
|
44
|
+
store_connection obj, options
|
|
45
|
+
end
|
|
46
|
+
|
|
14
47
|
@resource.broadcast
|
|
15
48
|
end
|
|
16
49
|
end
|
|
17
50
|
alias_method :<<, :push
|
|
18
51
|
|
|
19
|
-
|
|
20
|
-
|
|
52
|
+
##
|
|
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
|
|
55
|
+
# timeout a ConnectionPool::TimeoutError is raised.
|
|
56
|
+
#
|
|
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.
|
|
63
|
+
def pop(timeout = 0.5, options = {})
|
|
64
|
+
options, timeout = timeout, 0.5 if Hash === timeout
|
|
65
|
+
timeout = options.fetch :timeout, timeout
|
|
66
|
+
|
|
67
|
+
deadline = current_time + timeout
|
|
21
68
|
@mutex.synchronize do
|
|
22
69
|
loop do
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
70
|
+
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
71
|
+
if (conn = try_fetch_connection(options))
|
|
72
|
+
return conn
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
connection = try_create(options)
|
|
76
|
+
return connection if connection
|
|
77
|
+
|
|
78
|
+
to_wait = deadline - current_time
|
|
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
|
|
26
87
|
@resource.wait(@mutex, to_wait)
|
|
27
88
|
end
|
|
28
89
|
end
|
|
29
90
|
end
|
|
30
91
|
|
|
31
|
-
|
|
32
|
-
|
|
92
|
+
##
|
|
93
|
+
# Shuts down the TimedStack by passing each connection to +block+ and then
|
|
94
|
+
# removing it from the pool. Attempting to checkout a connection after
|
|
95
|
+
# shutdown will raise +ConnectionPool::PoolShuttingDownError+ unless
|
|
96
|
+
# +:reload+ is +true+.
|
|
97
|
+
def shutdown(reload: false, &block)
|
|
98
|
+
raise ArgumentError, "shutdown must receive a block" unless block
|
|
99
|
+
|
|
100
|
+
@mutex.synchronize do
|
|
101
|
+
@shutdown_block = block
|
|
102
|
+
@resource.broadcast
|
|
103
|
+
|
|
104
|
+
shutdown_connections
|
|
105
|
+
@shutdown_block = nil if reload
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Reaps connections that were checked in more than +idle_seconds+ ago.
|
|
111
|
+
def reap(idle_seconds, &block)
|
|
112
|
+
raise ArgumentError, "reap must receive a block" unless block
|
|
113
|
+
raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
|
|
114
|
+
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
115
|
+
|
|
116
|
+
idle.times do
|
|
117
|
+
conn =
|
|
118
|
+
@mutex.synchronize do
|
|
119
|
+
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
120
|
+
|
|
121
|
+
reserve_idle_connection(idle_seconds)
|
|
122
|
+
end
|
|
123
|
+
break unless conn
|
|
124
|
+
|
|
125
|
+
block.call(conn)
|
|
126
|
+
end
|
|
33
127
|
end
|
|
34
128
|
|
|
35
|
-
|
|
36
|
-
|
|
129
|
+
##
|
|
130
|
+
# Returns +true+ if there are no available connections.
|
|
131
|
+
def empty?
|
|
132
|
+
(@created - @que.length) >= @max
|
|
37
133
|
end
|
|
38
134
|
|
|
135
|
+
##
|
|
136
|
+
# The number of connections available on the stack.
|
|
39
137
|
def length
|
|
138
|
+
@max - @created + @que.length
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# The number of connections created and available on the stack.
|
|
143
|
+
def idle
|
|
40
144
|
@que.length
|
|
41
145
|
end
|
|
146
|
+
|
|
147
|
+
##
|
|
148
|
+
# Reduce the created count
|
|
149
|
+
def decrement_created
|
|
150
|
+
@created -= 1 unless @created == 0
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def current_time
|
|
156
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
##
|
|
160
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
161
|
+
#
|
|
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
|
|
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.
|
|
173
|
+
def connection_stored?(options = nil)
|
|
174
|
+
!@que.empty?
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
179
|
+
#
|
|
180
|
+
# This method must return a connection from the stack.
|
|
181
|
+
def fetch_connection(options = nil)
|
|
182
|
+
@que.pop&.first
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
##
|
|
186
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
187
|
+
#
|
|
188
|
+
# This method must shut down all connections on the stack.
|
|
189
|
+
def shutdown_connections(options = nil)
|
|
190
|
+
while (conn = try_fetch_connection(options))
|
|
191
|
+
@created -= 1 unless @created == 0
|
|
192
|
+
@shutdown_block.call(conn)
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
##
|
|
197
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
198
|
+
#
|
|
199
|
+
# This method returns the oldest idle connection if it has been idle for more than idle_seconds.
|
|
200
|
+
# This requires that the stack is kept in order of checked in time (oldest first).
|
|
201
|
+
def reserve_idle_connection(idle_seconds)
|
|
202
|
+
return unless idle_connections?(idle_seconds)
|
|
203
|
+
|
|
204
|
+
@created -= 1 unless @created == 0
|
|
205
|
+
|
|
206
|
+
@que.shift.first
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
##
|
|
210
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
211
|
+
#
|
|
212
|
+
# Returns true if the first connection in the stack has been idle for more than idle_seconds
|
|
213
|
+
def idle_connections?(idle_seconds)
|
|
214
|
+
connection_stored? && (current_time - @que.first.last > idle_seconds)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
##
|
|
218
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
219
|
+
#
|
|
220
|
+
# This method must return +obj+ to the stack.
|
|
221
|
+
def store_connection(obj, options = nil)
|
|
222
|
+
@que.push [obj, current_time]
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
##
|
|
226
|
+
# This is an extension point for TimedStack and is called with a mutex.
|
|
227
|
+
#
|
|
228
|
+
# This method must create a connection if and only if the total number of
|
|
229
|
+
# connections allowed has not been met.
|
|
230
|
+
def try_create(options = nil)
|
|
231
|
+
unless @created == @max
|
|
232
|
+
object = @create_block.call
|
|
233
|
+
@created += 1
|
|
234
|
+
object
|
|
235
|
+
end
|
|
236
|
+
end
|
|
42
237
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class ConnectionPool
|
|
2
|
+
class Wrapper < ::BasicObject
|
|
3
|
+
METHODS = [:with, :pool_shutdown, :wrapped_pool]
|
|
4
|
+
|
|
5
|
+
def initialize(options = {}, &block)
|
|
6
|
+
@pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def wrapped_pool
|
|
10
|
+
@pool
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def with(&block)
|
|
14
|
+
@pool.with(&block)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def pool_shutdown(&block)
|
|
18
|
+
@pool.shutdown(&block)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def pool_size
|
|
22
|
+
@pool.size
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def pool_available
|
|
26
|
+
@pool.available
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def respond_to?(id, *args)
|
|
30
|
+
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# rubocop:disable Style/MissingRespondToMissing
|
|
34
|
+
if ::RUBY_VERSION >= "3.0.0"
|
|
35
|
+
def method_missing(name, *args, **kwargs, &block)
|
|
36
|
+
with do |connection|
|
|
37
|
+
connection.send(name, *args, **kwargs, &block)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
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
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
# rubocop:enable Style/MethodMissingSuper
|
|
54
|
+
# rubocop:enable Style/MissingRespondToMissing
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/connection_pool.rb
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
require "timeout"
|
|
2
|
+
require_relative "connection_pool/version"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
class ConnectionPool
|
|
5
|
+
class Error < ::RuntimeError; end
|
|
6
|
+
|
|
7
|
+
class PoolShuttingDownError < ::ConnectionPool::Error; end
|
|
8
|
+
|
|
9
|
+
class TimeoutError < ::Timeout::Error; end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Generic connection pool class for sharing a limited number of objects or network connections
|
|
13
|
+
# among many threads. Note: pool elements are lazily created.
|
|
5
14
|
#
|
|
6
15
|
# Example usage with block (faster):
|
|
7
16
|
#
|
|
8
17
|
# @pool = ConnectionPool.new { Redis.new }
|
|
9
|
-
#
|
|
10
18
|
# @pool.with do |redis|
|
|
11
19
|
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
|
12
20
|
# end
|
|
13
21
|
#
|
|
22
|
+
# Using optional timeout override (for that single invocation)
|
|
23
|
+
#
|
|
24
|
+
# @pool.with(timeout: 2.0) do |redis|
|
|
25
|
+
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
|
26
|
+
# end
|
|
27
|
+
#
|
|
14
28
|
# Example usage replacing an existing connection (slower):
|
|
15
29
|
#
|
|
16
30
|
# $redis = ConnectionPool.wrap { Redis.new }
|
|
@@ -22,80 +36,198 @@ require_relative 'connection_pool/version'
|
|
|
22
36
|
# Accepts the following options:
|
|
23
37
|
# - :size - number of connections to pool, defaults to 5
|
|
24
38
|
# - :timeout - amount of time to wait for a connection if none currently available, defaults to 5 seconds
|
|
39
|
+
# - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true
|
|
25
40
|
#
|
|
26
41
|
class ConnectionPool
|
|
27
|
-
DEFAULTS = {size: 5, timeout: 5}
|
|
42
|
+
DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}.freeze
|
|
28
43
|
|
|
29
44
|
def self.wrap(options, &block)
|
|
30
45
|
Wrapper.new(options, &block)
|
|
31
46
|
end
|
|
32
47
|
|
|
48
|
+
if Process.respond_to?(:fork)
|
|
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
|
+
|
|
33
90
|
def initialize(options = {}, &block)
|
|
34
|
-
raise ArgumentError,
|
|
91
|
+
raise ArgumentError, "Connection pool requires a block" unless block
|
|
35
92
|
|
|
36
93
|
options = DEFAULTS.merge(options)
|
|
37
94
|
|
|
38
|
-
@size = options.fetch(:size)
|
|
95
|
+
@size = Integer(options.fetch(:size))
|
|
39
96
|
@timeout = options.fetch(:timeout)
|
|
97
|
+
@auto_reload_after_fork = options.fetch(:auto_reload_after_fork)
|
|
40
98
|
|
|
41
99
|
@available = TimedStack.new(@size, &block)
|
|
42
|
-
@key = :"
|
|
100
|
+
@key = :"pool-#{@available.object_id}"
|
|
101
|
+
@key_count = :"pool-#{@available.object_id}-count"
|
|
102
|
+
@discard_key = :"pool-#{@available.object_id}-discard"
|
|
103
|
+
INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES
|
|
43
104
|
end
|
|
44
105
|
|
|
45
|
-
def with
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
110
|
+
Thread.handle_interrupt(Exception => :never) do
|
|
111
|
+
conn = checkout(options)
|
|
112
|
+
begin
|
|
113
|
+
Thread.handle_interrupt(Exception => :immediate) do
|
|
114
|
+
yield conn
|
|
115
|
+
end
|
|
116
|
+
ensure
|
|
117
|
+
checkin
|
|
118
|
+
end
|
|
51
119
|
end
|
|
52
120
|
end
|
|
121
|
+
alias_method :then, :with
|
|
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
|
|
53
156
|
|
|
54
|
-
def checkout
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
conn = @available.pop(@timeout)
|
|
157
|
+
def checkout(options = {})
|
|
158
|
+
if ::Thread.current[@key]
|
|
159
|
+
::Thread.current[@key_count] += 1
|
|
160
|
+
::Thread.current[@key]
|
|
59
161
|
else
|
|
60
|
-
|
|
162
|
+
::Thread.current[@key_count] = 1
|
|
163
|
+
::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options)
|
|
61
164
|
end
|
|
62
|
-
|
|
63
|
-
stack.push conn
|
|
64
|
-
conn
|
|
65
165
|
end
|
|
66
166
|
|
|
67
|
-
def checkin
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
167
|
+
def checkin(force: false)
|
|
168
|
+
if ::Thread.current[@key]
|
|
169
|
+
if ::Thread.current[@key_count] == 1 || force
|
|
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
|
|
182
|
+
::Thread.current[@key] = nil
|
|
183
|
+
::Thread.current[@key_count] = nil
|
|
184
|
+
else
|
|
185
|
+
::Thread.current[@key_count] -= 1
|
|
186
|
+
end
|
|
187
|
+
elsif !force
|
|
188
|
+
raise ConnectionPool::Error, "no connections are checked out"
|
|
72
189
|
end
|
|
190
|
+
|
|
73
191
|
nil
|
|
74
192
|
end
|
|
75
193
|
|
|
76
|
-
|
|
77
|
-
|
|
194
|
+
##
|
|
195
|
+
# Shuts down the ConnectionPool by passing each connection to +block+ and
|
|
196
|
+
# then removing it from the pool. Attempting to checkout a connection after
|
|
197
|
+
# shutdown will raise +ConnectionPool::PoolShuttingDownError+.
|
|
198
|
+
def shutdown(&block)
|
|
199
|
+
@available.shutdown(&block)
|
|
200
|
+
end
|
|
78
201
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
202
|
+
##
|
|
203
|
+
# Reloads the ConnectionPool by passing each connection to +block+ and then
|
|
204
|
+
# removing it the pool. Subsequent checkouts will create new connections as
|
|
205
|
+
# needed.
|
|
206
|
+
def reload(&block)
|
|
207
|
+
@available.shutdown(reload: true, &block)
|
|
208
|
+
end
|
|
82
209
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
210
|
+
## Reaps idle connections that have been idle for over +idle_seconds+.
|
|
211
|
+
# +idle_seconds+ defaults to 60.
|
|
212
|
+
def reap(idle_seconds = 60, &block)
|
|
213
|
+
@available.reap(idle_seconds, &block)
|
|
214
|
+
end
|
|
88
215
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
216
|
+
# Size of this connection pool
|
|
217
|
+
attr_reader :size
|
|
218
|
+
# Automatically drop all connections after fork
|
|
219
|
+
attr_reader :auto_reload_after_fork
|
|
92
220
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
221
|
+
# Number of pool entries available for checkout at this instant.
|
|
222
|
+
def available
|
|
223
|
+
@available.length
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Number of pool entries created and idle in the pool.
|
|
227
|
+
def idle
|
|
228
|
+
@available.idle
|
|
98
229
|
end
|
|
99
230
|
end
|
|
100
231
|
|
|
101
|
-
require_relative
|
|
232
|
+
require_relative "connection_pool/timed_stack"
|
|
233
|
+
require_relative "connection_pool/wrapper"
|
metadata
CHANGED
|
@@ -1,59 +1,94 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: connection_pool
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 2.5.5
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Mike Perham
|
|
9
|
-
|
|
8
|
+
- Damian Janowski
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
13
|
-
dependencies:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: minitest
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 5.0.0
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 5.0.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
14
55
|
description: Generic connection pool for Ruby
|
|
15
56
|
email:
|
|
16
57
|
- mperham@gmail.com
|
|
58
|
+
- damian@educabilia.com
|
|
17
59
|
executables: []
|
|
18
60
|
extensions: []
|
|
19
61
|
extra_rdoc_files: []
|
|
20
62
|
files:
|
|
21
|
-
- .gitignore
|
|
22
63
|
- Changes.md
|
|
23
|
-
- Gemfile
|
|
24
64
|
- LICENSE
|
|
25
65
|
- README.md
|
|
26
|
-
- Rakefile
|
|
27
66
|
- connection_pool.gemspec
|
|
28
67
|
- lib/connection_pool.rb
|
|
29
68
|
- lib/connection_pool/timed_stack.rb
|
|
30
69
|
- lib/connection_pool/version.rb
|
|
31
|
-
-
|
|
32
|
-
- test/test_connection_pool.rb
|
|
70
|
+
- lib/connection_pool/wrapper.rb
|
|
33
71
|
homepage: https://github.com/mperham/connection_pool
|
|
34
|
-
licenses:
|
|
35
|
-
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
changelog_uri: https://github.com/mperham/connection_pool/blob/main/Changes.md
|
|
76
|
+
rubygems_mfa_required: 'true'
|
|
36
77
|
rdoc_options: []
|
|
37
78
|
require_paths:
|
|
38
79
|
- lib
|
|
39
80
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
40
|
-
none: false
|
|
41
81
|
requirements:
|
|
42
|
-
- -
|
|
82
|
+
- - ">="
|
|
43
83
|
- !ruby/object:Gem::Version
|
|
44
|
-
version:
|
|
84
|
+
version: 2.5.0
|
|
45
85
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
46
|
-
none: false
|
|
47
86
|
requirements:
|
|
48
|
-
- -
|
|
87
|
+
- - ">="
|
|
49
88
|
- !ruby/object:Gem::Version
|
|
50
89
|
version: '0'
|
|
51
90
|
requirements: []
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
signing_key:
|
|
55
|
-
specification_version: 3
|
|
91
|
+
rubygems_version: 3.6.9
|
|
92
|
+
specification_version: 4
|
|
56
93
|
summary: Generic connection pool for Ruby
|
|
57
|
-
test_files:
|
|
58
|
-
- test/helper.rb
|
|
59
|
-
- test/test_connection_pool.rb
|
|
94
|
+
test_files: []
|
data/.gitignore
DELETED
data/Gemfile
DELETED
data/Rakefile
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
begin
|
|
2
|
-
require 'bundler'
|
|
3
|
-
Bundler::GemHelper.install_tasks
|
|
4
|
-
rescue LoadError
|
|
5
|
-
end
|
|
6
|
-
|
|
7
|
-
require 'rake/testtask'
|
|
8
|
-
Rake::TestTask.new(:test) do |test|
|
|
9
|
-
test.libs << 'test'
|
|
10
|
-
test.warning = true
|
|
11
|
-
test.pattern = 'test/**/test_*.rb'
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
task :default => :test
|
data/test/helper.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
require 'rubygems'
|
|
2
|
-
require 'minitest/pride'
|
|
3
|
-
require 'minitest/autorun'
|
|
4
|
-
|
|
5
|
-
puts RUBY_DESCRIPTION
|
|
6
|
-
|
|
7
|
-
class MiniTest::Unit::TestCase
|
|
8
|
-
|
|
9
|
-
def async_test(time=0.5)
|
|
10
|
-
q = TimedQueue.new
|
|
11
|
-
yield Proc.new { q << nil }
|
|
12
|
-
q.timed_pop(time)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
$VERBOSE = 1
|
|
18
|
-
|
|
19
|
-
require_relative '../lib/connection_pool'
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
Thread.abort_on_exception = true
|
|
2
|
-
require 'helper'
|
|
3
|
-
|
|
4
|
-
class TestConnectionPool < MiniTest::Unit::TestCase
|
|
5
|
-
|
|
6
|
-
class NetworkConnection
|
|
7
|
-
def initialize
|
|
8
|
-
@x = 0
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def do_something
|
|
12
|
-
@x += 1
|
|
13
|
-
sleep 0.05
|
|
14
|
-
@x
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def fast
|
|
18
|
-
@x += 1
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def do_something_with_block
|
|
22
|
-
@x += yield
|
|
23
|
-
sleep 0.05
|
|
24
|
-
@x
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def respond_to?(method_id, *args)
|
|
28
|
-
method_id == :do_magic || super(method_id, *args)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def test_basic_multithreaded_usage
|
|
33
|
-
pool = ConnectionPool.new(:size => 5) { NetworkConnection.new }
|
|
34
|
-
threads = []
|
|
35
|
-
15.times do
|
|
36
|
-
threads << Thread.new do
|
|
37
|
-
pool.with do |net|
|
|
38
|
-
net.do_something
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
a = Time.now
|
|
44
|
-
result = threads.map(&:value)
|
|
45
|
-
b = Time.now
|
|
46
|
-
assert_operator((b - a), :>, 0.125)
|
|
47
|
-
assert_equal([1,2,3].cycle(5).sort, result.sort)
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def test_timeout
|
|
51
|
-
pool = ConnectionPool.new(:timeout => 0.05, :size => 1) { NetworkConnection.new }
|
|
52
|
-
Thread.new do
|
|
53
|
-
pool.with do |net|
|
|
54
|
-
net.do_something
|
|
55
|
-
sleep 0.1
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
sleep 0.05
|
|
59
|
-
assert_raises Timeout::Error do
|
|
60
|
-
pool.with { |net| net.do_something }
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
sleep 0.05
|
|
64
|
-
pool.with do |conn|
|
|
65
|
-
refute_nil conn
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def test_passthru
|
|
70
|
-
pool = ConnectionPool.wrap(:timeout => 0.1, :size => 1) { NetworkConnection.new }
|
|
71
|
-
assert_equal 1, pool.do_something
|
|
72
|
-
assert_equal 2, pool.do_something
|
|
73
|
-
assert_equal 5, pool.do_something_with_block { 3 }
|
|
74
|
-
assert_equal 6, pool.with { |net| net.fast }
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def test_passthru_respond_to
|
|
78
|
-
pool = ConnectionPool.wrap(:timeout => 0.1, :size => 1) { NetworkConnection.new }
|
|
79
|
-
assert pool.respond_to?(:with)
|
|
80
|
-
assert pool.respond_to?(:do_something)
|
|
81
|
-
assert pool.respond_to?(:do_magic)
|
|
82
|
-
refute pool.respond_to?(:do_lots_of_magic)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def test_return_value
|
|
86
|
-
pool = ConnectionPool.new(:timeout => 0.1, :size => 1) { NetworkConnection.new }
|
|
87
|
-
result = pool.with do |net|
|
|
88
|
-
net.fast
|
|
89
|
-
end
|
|
90
|
-
assert_equal 1, result
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def test_heavy_threading
|
|
94
|
-
pool = ConnectionPool.new(:timeout => 0.5, :size => 3) { NetworkConnection.new }
|
|
95
|
-
20.times do
|
|
96
|
-
Thread.new do
|
|
97
|
-
pool.with do |net|
|
|
98
|
-
sleep 0.05
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
sleep 0.5
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def test_reuses_objects_when_pool_not_saturated
|
|
106
|
-
pool = ConnectionPool.new(:size => 5) { NetworkConnection.new }
|
|
107
|
-
|
|
108
|
-
ids = 10.times.map do
|
|
109
|
-
pool.with { |c| c.object_id }
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
assert_equal 1, ids.uniq.size
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
class Recorder
|
|
116
|
-
def initialize
|
|
117
|
-
@calls = []
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
attr_reader :calls
|
|
121
|
-
|
|
122
|
-
def do_work(label)
|
|
123
|
-
@calls << label
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def test_nested_checkout
|
|
128
|
-
recorder = Recorder.new
|
|
129
|
-
pool = ConnectionPool.new(:size => 1) { recorder }
|
|
130
|
-
pool.with do |r_outer|
|
|
131
|
-
@other = Thread.new do |t|
|
|
132
|
-
pool.with do |r_other|
|
|
133
|
-
r_other.do_work('other')
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
pool.with do |r_inner|
|
|
138
|
-
r_inner.do_work('inner')
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
sleep 0.1
|
|
142
|
-
|
|
143
|
-
r_outer.do_work('outer')
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
@other.join
|
|
147
|
-
|
|
148
|
-
assert_equal ['inner', 'outer', 'other'], recorder.calls
|
|
149
|
-
end
|
|
150
|
-
end
|