connection_pool 2.2.2 → 2.4.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 +5 -5
- data/Changes.md +31 -2
- data/README.md +59 -31
- data/connection_pool.gemspec +15 -14
- data/lib/connection_pool/timed_stack.rb +19 -21
- data/lib/connection_pool/version.rb +1 -1
- data/lib/connection_pool/wrapper.rb +56 -0
- data/lib/connection_pool.rb +86 -77
- metadata +9 -20
- data/.gitignore +0 -4
- data/.travis.yml +0 -10
- data/Gemfile +0 -3
- data/Rakefile +0 -6
- data/lib/connection_pool/monotonic_time.rb +0 -66
- data/test/helper.rb +0 -8
- data/test/test_connection_pool.rb +0 -516
- data/test/test_connection_pool_timed_stack.rb +0 -149
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e87682a6e57e8b0214de9c0713a7466a2b06399dc52d700fbf182cdf9e5b6606
|
|
4
|
+
data.tar.gz: d12337513b62d4677663403c512afad165275bdf848987182ce03637a1de6482
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eca2c1f8ebe52039f00df70ddfe5525cc0408acd8ae9849a8d1412bec670e7d3a05d3609db933e3e906cd83307d39f754dd5f1d7b48b50b847090b5bf485b55a
|
|
7
|
+
data.tar.gz: fc9a62b4b0ba5a406543e8f6399bd566f2f31c6c9a6ce2772e3550c1c2a75945c8a34defeda10c83e23cbfe5fe50d0e8861f57ea23935d7c97540aa97b04a635
|
data/Changes.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# connection_pool Changelog
|
|
2
|
+
|
|
3
|
+
2.4.0
|
|
4
|
+
------
|
|
5
|
+
|
|
6
|
+
- Automatically drop all connections after fork [#166]
|
|
7
|
+
|
|
8
|
+
2.3.0
|
|
9
|
+
------
|
|
10
|
+
|
|
11
|
+
- Minimum Ruby version is now 2.5.0
|
|
12
|
+
- Add pool size to TimeoutError message
|
|
13
|
+
|
|
14
|
+
2.2.5
|
|
15
|
+
------
|
|
16
|
+
|
|
17
|
+
- Fix argument forwarding on Ruby 2.7 [#149]
|
|
18
|
+
|
|
19
|
+
2.2.4
|
|
20
|
+
------
|
|
21
|
+
|
|
22
|
+
- Add `reload` to close all connections, recreating them afterwards [Andrew Marshall, #140]
|
|
23
|
+
- Add `then` as a way to use a pool or a bare connection with the same code path [#138]
|
|
24
|
+
|
|
25
|
+
2.2.3
|
|
26
|
+
------
|
|
27
|
+
|
|
28
|
+
- Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130]
|
|
29
|
+
- Use monotonic clock present in all modern Rubies [Tero Tasanen, #109]
|
|
30
|
+
- Remove code hacks necessary for JRuby 1.7
|
|
31
|
+
- Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113]
|
|
3
32
|
|
|
4
33
|
2.2.2
|
|
5
34
|
------
|
data/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
connection\_pool
|
|
2
2
|
=================
|
|
3
|
-
[](https://github.com/mperham/connection_pool/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
5
|
Generic connection pooling for Ruby.
|
|
6
6
|
|
|
7
|
-
MongoDB has its own connection pool.
|
|
8
|
-
|
|
9
|
-
Dalli and other Ruby network clients.
|
|
10
|
-
|
|
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.
|
|
11
10
|
|
|
12
11
|
Usage
|
|
13
12
|
-----
|
|
14
13
|
|
|
15
|
-
Create a pool of objects to share amongst the fibers or threads in your Ruby
|
|
16
|
-
application:
|
|
14
|
+
Create a pool of objects to share amongst the fibers or threads in your Ruby application:
|
|
17
15
|
|
|
18
16
|
``` ruby
|
|
19
17
|
$memcached = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new }
|
|
@@ -28,8 +26,17 @@ end
|
|
|
28
26
|
```
|
|
29
27
|
|
|
30
28
|
If all the objects in the connection pool are in use, `with` will block
|
|
31
|
-
until one becomes available.
|
|
32
|
-
|
|
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`).
|
|
32
|
+
|
|
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
|
+
```
|
|
33
40
|
|
|
34
41
|
Optionally, you can specify a timeout override using the with-block semantics:
|
|
35
42
|
|
|
@@ -40,26 +47,23 @@ end
|
|
|
40
47
|
```
|
|
41
48
|
|
|
42
49
|
This will only modify the resource-get timeout for this particular
|
|
43
|
-
invocation.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
`ConnectionPool::Wrapper` class.
|
|
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.
|
|
47
54
|
|
|
48
55
|
## Migrating to a Connection Pool
|
|
49
56
|
|
|
50
|
-
You can use `ConnectionPool::Wrapper` to wrap a single global connection,
|
|
51
|
-
making it easier to migrate existing connection code over time:
|
|
57
|
+
You can use `ConnectionPool::Wrapper` to wrap a single global connection, making it easier to migrate existing connection code over time:
|
|
52
58
|
|
|
53
59
|
``` ruby
|
|
54
|
-
$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.
|
|
60
|
+
$redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
|
|
55
61
|
$redis.sadd('foo', 1)
|
|
56
62
|
$redis.smembers('foo')
|
|
57
63
|
```
|
|
58
64
|
|
|
59
|
-
The wrapper uses `method_missing` to checkout a connection, run the requested
|
|
60
|
-
|
|
61
|
-
**not** high-performance so you'll want to port your performance sensitive code
|
|
62
|
-
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.
|
|
63
67
|
|
|
64
68
|
``` ruby
|
|
65
69
|
$redis.with do |conn|
|
|
@@ -68,40 +72,64 @@ $redis.with do |conn|
|
|
|
68
72
|
end
|
|
69
73
|
```
|
|
70
74
|
|
|
71
|
-
Once you've ported your entire system to use `with`, you can simply remove
|
|
72
|
-
`Wrapper` and use the simpler and faster `ConnectionPool`.
|
|
75
|
+
Once you've ported your entire system to use `with`, you can simply remove `Wrapper` and use the simpler and faster `ConnectionPool`.
|
|
73
76
|
|
|
74
77
|
|
|
75
78
|
## Shutdown
|
|
76
79
|
|
|
77
80
|
You can shut down a ConnectionPool instance once it should no longer be used.
|
|
78
|
-
Further checkout attempts will immediately raise an error but existing checkouts
|
|
79
|
-
will work.
|
|
81
|
+
Further checkout attempts will immediately raise an error but existing checkouts will work.
|
|
80
82
|
|
|
81
83
|
```ruby
|
|
82
84
|
cp = ConnectionPool.new { Redis.new }
|
|
83
|
-
cp.shutdown { |
|
|
85
|
+
cp.shutdown { |c| c.close }
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
Shutting down a connection pool will block until all connections are checked in and closed.
|
|
87
|
-
**Note that shutting down is completely optional**; Ruby's garbage collector will reclaim
|
|
88
|
-
|
|
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
|
+
## Current State
|
|
105
|
+
|
|
106
|
+
There are several methods that return information about a pool.
|
|
89
107
|
|
|
108
|
+
```ruby
|
|
109
|
+
cp = ConnectionPool.new(size: 10) { Redis.new }
|
|
110
|
+
cp.size # => 10
|
|
111
|
+
cp.available # => 10
|
|
112
|
+
|
|
113
|
+
cp.with do |conn|
|
|
114
|
+
cp.size # => 10
|
|
115
|
+
cp.available # => 9
|
|
116
|
+
end
|
|
117
|
+
```
|
|
90
118
|
|
|
91
119
|
Notes
|
|
92
120
|
-----
|
|
93
121
|
|
|
94
122
|
- Connections are lazily created as needed.
|
|
95
123
|
- There is no provision for repairing or checking the health of a connection;
|
|
96
|
-
connections should be self-repairing.
|
|
124
|
+
connections should be self-repairing. This is true of the Dalli and Redis
|
|
97
125
|
clients.
|
|
98
126
|
- **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
|
|
99
|
-
occasional silent corruption and mysterious errors.
|
|
100
|
-
and cannot be used correctly, ever.
|
|
127
|
+
occasional silent corruption and mysterious errors. The Timeout API is unsafe
|
|
128
|
+
and cannot be used correctly, ever. Use proper socket timeout options as
|
|
101
129
|
exposed by Net::HTTP, Redis, Dalli, etc.
|
|
102
130
|
|
|
103
131
|
|
|
104
132
|
Author
|
|
105
133
|
------
|
|
106
134
|
|
|
107
|
-
Mike Perham, [@
|
|
135
|
+
Mike Perham, [@getajobmike](https://twitter.com/getajobmike), <https://www.mikeperham.com>
|
data/connection_pool.gemspec
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
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
17
|
s.license = "MIT"
|
|
18
|
-
s.add_development_dependency
|
|
19
|
-
s.add_development_dependency
|
|
20
|
-
s.add_development_dependency
|
|
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"
|
|
21
22
|
end
|
|
@@ -1,13 +1,3 @@
|
|
|
1
|
-
require 'thread'
|
|
2
|
-
require 'timeout'
|
|
3
|
-
require_relative 'monotonic_time'
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
# Raised when you attempt to retrieve a connection from a pool that has been
|
|
7
|
-
# shut down.
|
|
8
|
-
|
|
9
|
-
class ConnectionPool::PoolShuttingDownError < RuntimeError; end
|
|
10
|
-
|
|
11
1
|
##
|
|
12
2
|
# The TimedStack manages a pool of homogeneous connections (or any resource
|
|
13
3
|
# you wish to manage). Connections are created lazily up to a given maximum
|
|
@@ -25,7 +15,7 @@ class ConnectionPool::PoolShuttingDownError < RuntimeError; end
|
|
|
25
15
|
#
|
|
26
16
|
# conn = ts.pop
|
|
27
17
|
# ts.pop timeout: 5
|
|
28
|
-
# #=> raises
|
|
18
|
+
# #=> raises ConnectionPool::TimeoutError after 5 seconds
|
|
29
19
|
|
|
30
20
|
class ConnectionPool::TimedStack
|
|
31
21
|
attr_reader :max
|
|
@@ -39,8 +29,8 @@ class ConnectionPool::TimedStack
|
|
|
39
29
|
@created = 0
|
|
40
30
|
@que = []
|
|
41
31
|
@max = size
|
|
42
|
-
@mutex = Mutex.new
|
|
43
|
-
@resource = ConditionVariable.new
|
|
32
|
+
@mutex = Thread::Mutex.new
|
|
33
|
+
@resource = Thread::ConditionVariable.new
|
|
44
34
|
@shutdown_block = nil
|
|
45
35
|
end
|
|
46
36
|
|
|
@@ -64,7 +54,7 @@ class ConnectionPool::TimedStack
|
|
|
64
54
|
##
|
|
65
55
|
# Retrieves a connection from the stack. If a connection is available it is
|
|
66
56
|
# immediately returned. If no connection is available within the given
|
|
67
|
-
# timeout a
|
|
57
|
+
# timeout a ConnectionPool::TimeoutError is raised.
|
|
68
58
|
#
|
|
69
59
|
# +:timeout+ is the only checked entry in +options+ and is preferred over
|
|
70
60
|
# the +timeout+ argument (which will be removed in a future release). Other
|
|
@@ -74,7 +64,7 @@ class ConnectionPool::TimedStack
|
|
|
74
64
|
options, timeout = timeout, 0.5 if Hash === timeout
|
|
75
65
|
timeout = options.fetch :timeout, timeout
|
|
76
66
|
|
|
77
|
-
deadline =
|
|
67
|
+
deadline = current_time + timeout
|
|
78
68
|
@mutex.synchronize do
|
|
79
69
|
loop do
|
|
80
70
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
@@ -83,25 +73,28 @@ class ConnectionPool::TimedStack
|
|
|
83
73
|
connection = try_create(options)
|
|
84
74
|
return connection if connection
|
|
85
75
|
|
|
86
|
-
to_wait = deadline -
|
|
87
|
-
raise
|
|
76
|
+
to_wait = deadline - current_time
|
|
77
|
+
raise ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0
|
|
88
78
|
@resource.wait(@mutex, to_wait)
|
|
89
79
|
end
|
|
90
80
|
end
|
|
91
81
|
end
|
|
92
82
|
|
|
93
83
|
##
|
|
94
|
-
# Shuts down the TimedStack
|
|
95
|
-
#
|
|
84
|
+
# Shuts down the TimedStack by passing each connection to +block+ and then
|
|
85
|
+
# removing it from the pool. Attempting to checkout a connection after
|
|
86
|
+
# shutdown will raise +ConnectionPool::PoolShuttingDownError+ unless
|
|
87
|
+
# +:reload+ is +true+.
|
|
96
88
|
|
|
97
|
-
def shutdown(&block)
|
|
98
|
-
raise ArgumentError, "shutdown must receive a block" unless
|
|
89
|
+
def shutdown(reload: false, &block)
|
|
90
|
+
raise ArgumentError, "shutdown must receive a block" unless block
|
|
99
91
|
|
|
100
92
|
@mutex.synchronize do
|
|
101
93
|
@shutdown_block = block
|
|
102
94
|
@resource.broadcast
|
|
103
95
|
|
|
104
96
|
shutdown_connections
|
|
97
|
+
@shutdown_block = nil if reload
|
|
105
98
|
end
|
|
106
99
|
end
|
|
107
100
|
|
|
@@ -121,6 +114,10 @@ class ConnectionPool::TimedStack
|
|
|
121
114
|
|
|
122
115
|
private
|
|
123
116
|
|
|
117
|
+
def current_time
|
|
118
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
119
|
+
end
|
|
120
|
+
|
|
124
121
|
##
|
|
125
122
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
126
123
|
#
|
|
@@ -149,6 +146,7 @@ class ConnectionPool::TimedStack
|
|
|
149
146
|
conn = fetch_connection(options)
|
|
150
147
|
@shutdown_block.call(conn)
|
|
151
148
|
end
|
|
149
|
+
@created = 0
|
|
152
150
|
end
|
|
153
151
|
|
|
154
152
|
##
|
|
@@ -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,14 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
require_relative
|
|
1
|
+
require "timeout"
|
|
2
|
+
require_relative "connection_pool/version"
|
|
3
3
|
|
|
4
|
+
class ConnectionPool
|
|
5
|
+
class Error < ::RuntimeError; end
|
|
6
|
+
|
|
7
|
+
class PoolShuttingDownError < ::ConnectionPool::Error; end
|
|
8
|
+
|
|
9
|
+
class TimeoutError < ::Timeout::Error; end
|
|
10
|
+
end
|
|
4
11
|
|
|
5
|
-
# Generic connection pool class for
|
|
6
|
-
# among many threads. Note:
|
|
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.
|
|
7
14
|
#
|
|
8
15
|
# Example usage with block (faster):
|
|
9
16
|
#
|
|
10
17
|
# @pool = ConnectionPool.new { Redis.new }
|
|
11
|
-
#
|
|
12
18
|
# @pool.with do |redis|
|
|
13
19
|
# redis.lpop('my-list') if redis.llen('my-list') > 0
|
|
14
20
|
# end
|
|
@@ -34,29 +40,65 @@ require_relative 'connection_pool/timed_stack'
|
|
|
34
40
|
class ConnectionPool
|
|
35
41
|
DEFAULTS = {size: 5, timeout: 5}
|
|
36
42
|
|
|
37
|
-
class Error < RuntimeError
|
|
38
|
-
end
|
|
39
|
-
|
|
40
43
|
def self.wrap(options, &block)
|
|
41
44
|
Wrapper.new(options, &block)
|
|
42
45
|
end
|
|
43
46
|
|
|
47
|
+
if Process.respond_to?(:fork)
|
|
48
|
+
INSTANCES = ObjectSpace::WeakMap.new
|
|
49
|
+
private_constant :INSTANCES
|
|
50
|
+
|
|
51
|
+
def self.after_fork
|
|
52
|
+
INSTANCES.values.each do |pool|
|
|
53
|
+
# We're on after fork, so we know all other threads are dead.
|
|
54
|
+
# All we need to do is to ensure the main thread doesn't have a
|
|
55
|
+
# checked out connection
|
|
56
|
+
pool.checkin(force: true)
|
|
57
|
+
|
|
58
|
+
pool.reload do |connection|
|
|
59
|
+
# Unfortunately we don't know what method to call to close the connection,
|
|
60
|
+
# so we try the most common one.
|
|
61
|
+
connection.close if connection.respond_to?(:close)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
nil
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if ::Process.respond_to?(:_fork) # MRI 3.1+
|
|
68
|
+
module ForkTracker
|
|
69
|
+
def _fork
|
|
70
|
+
pid = super
|
|
71
|
+
if pid == 0
|
|
72
|
+
ConnectionPool.after_fork
|
|
73
|
+
end
|
|
74
|
+
pid
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
Process.singleton_class.prepend(ForkTracker)
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
INSTANCES = nil
|
|
81
|
+
private_constant :INSTANCES
|
|
82
|
+
|
|
83
|
+
def self.after_fork
|
|
84
|
+
# noop
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
44
88
|
def initialize(options = {}, &block)
|
|
45
|
-
raise ArgumentError,
|
|
89
|
+
raise ArgumentError, "Connection pool requires a block" unless block
|
|
46
90
|
|
|
47
91
|
options = DEFAULTS.merge(options)
|
|
48
92
|
|
|
49
|
-
@size = options.fetch(:size)
|
|
93
|
+
@size = Integer(options.fetch(:size))
|
|
50
94
|
@timeout = options.fetch(:timeout)
|
|
51
95
|
|
|
52
96
|
@available = TimedStack.new(@size, &block)
|
|
53
|
-
@key = :"
|
|
54
|
-
@key_count = :"
|
|
97
|
+
@key = :"pool-#{@available.object_id}"
|
|
98
|
+
@key_count = :"pool-#{@available.object_id}-count"
|
|
99
|
+
INSTANCES[self] = self if INSTANCES
|
|
55
100
|
end
|
|
56
101
|
|
|
57
|
-
if Thread.respond_to?(:handle_interrupt)
|
|
58
|
-
|
|
59
|
-
# MRI
|
|
60
102
|
def with(options = {})
|
|
61
103
|
Thread.handle_interrupt(Exception => :never) do
|
|
62
104
|
conn = checkout(options)
|
|
@@ -69,93 +111,60 @@ if Thread.respond_to?(:handle_interrupt)
|
|
|
69
111
|
end
|
|
70
112
|
end
|
|
71
113
|
end
|
|
72
|
-
|
|
73
|
-
else
|
|
74
|
-
|
|
75
|
-
# jruby 1.7.x
|
|
76
|
-
def with(options = {})
|
|
77
|
-
conn = checkout(options)
|
|
78
|
-
begin
|
|
79
|
-
yield conn
|
|
80
|
-
ensure
|
|
81
|
-
checkin
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
end
|
|
114
|
+
alias_method :then, :with
|
|
86
115
|
|
|
87
116
|
def checkout(options = {})
|
|
88
117
|
if ::Thread.current[@key]
|
|
89
|
-
::Thread.current[@key_count]+= 1
|
|
118
|
+
::Thread.current[@key_count] += 1
|
|
90
119
|
::Thread.current[@key]
|
|
91
120
|
else
|
|
92
|
-
::Thread.current[@key_count]= 1
|
|
93
|
-
::Thread.current[@key]= @available.pop(options[:timeout] || @timeout)
|
|
121
|
+
::Thread.current[@key_count] = 1
|
|
122
|
+
::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
|
|
94
123
|
end
|
|
95
124
|
end
|
|
96
125
|
|
|
97
|
-
def checkin
|
|
126
|
+
def checkin(force: false)
|
|
98
127
|
if ::Thread.current[@key]
|
|
99
|
-
if ::Thread.current[@key_count] == 1
|
|
128
|
+
if ::Thread.current[@key_count] == 1 || force
|
|
100
129
|
@available.push(::Thread.current[@key])
|
|
101
|
-
::Thread.current[@key]= nil
|
|
130
|
+
::Thread.current[@key] = nil
|
|
131
|
+
::Thread.current[@key_count] = nil
|
|
102
132
|
else
|
|
103
|
-
::Thread.current[@key_count]-= 1
|
|
133
|
+
::Thread.current[@key_count] -= 1
|
|
104
134
|
end
|
|
105
|
-
|
|
106
|
-
raise ConnectionPool::Error,
|
|
135
|
+
elsif !force
|
|
136
|
+
raise ConnectionPool::Error, "no connections are checked out"
|
|
107
137
|
end
|
|
108
138
|
|
|
109
139
|
nil
|
|
110
140
|
end
|
|
111
141
|
|
|
142
|
+
##
|
|
143
|
+
# Shuts down the ConnectionPool by passing each connection to +block+ and
|
|
144
|
+
# then removing it from the pool. Attempting to checkout a connection after
|
|
145
|
+
# shutdown will raise +ConnectionPool::PoolShuttingDownError+.
|
|
146
|
+
|
|
112
147
|
def shutdown(&block)
|
|
113
148
|
@available.shutdown(&block)
|
|
114
149
|
end
|
|
115
150
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
151
|
+
##
|
|
152
|
+
# Reloads the ConnectionPool by passing each connection to +block+ and then
|
|
153
|
+
# removing it the pool. Subsequent checkouts will create new connections as
|
|
154
|
+
# needed.
|
|
155
|
+
|
|
156
|
+
def reload(&block)
|
|
157
|
+
@available.shutdown(reload: true, &block)
|
|
119
158
|
end
|
|
120
159
|
|
|
160
|
+
# Size of this connection pool
|
|
161
|
+
attr_reader :size
|
|
162
|
+
|
|
121
163
|
# Number of pool entries available for checkout at this instant.
|
|
122
164
|
def available
|
|
123
165
|
@available.length
|
|
124
166
|
end
|
|
125
|
-
|
|
126
|
-
private
|
|
127
|
-
|
|
128
|
-
class Wrapper < ::BasicObject
|
|
129
|
-
METHODS = [:with, :pool_shutdown]
|
|
130
|
-
|
|
131
|
-
def initialize(options = {}, &block)
|
|
132
|
-
@pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) }
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def with(&block)
|
|
136
|
-
@pool.with(&block)
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
def pool_shutdown(&block)
|
|
140
|
-
@pool.shutdown(&block)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def pool_size
|
|
144
|
-
@pool.size
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def pool_available
|
|
148
|
-
@pool.available
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
def respond_to?(id, *args)
|
|
152
|
-
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def method_missing(name, *args, &block)
|
|
156
|
-
with do |connection|
|
|
157
|
-
connection.send(name, *args, &block)
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
167
|
end
|
|
168
|
+
|
|
169
|
+
require_relative "connection_pool/timed_stack"
|
|
170
|
+
require_relative "connection_pool/wrapper"
|