connection_pool 2.2.1 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Changes.md +42 -2
- data/README.md +59 -31
- data/connection_pool.gemspec +17 -14
- data/lib/connection_pool/timed_stack.rb +20 -21
- data/lib/connection_pool/version.rb +1 -1
- data/lib/connection_pool/wrapper.rb +56 -0
- data/lib/connection_pool.rb +100 -73
- metadata +12 -22
- data/.gitignore +0 -4
- data/.travis.yml +0 -14
- 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 -501
- 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: ea0776fcb09a3cc48ef4ca03774399e20b09e51039d0c47c1e4cb3bac621c52b
|
|
4
|
+
data.tar.gz: b955d6b4e984259f20ae8cf6414f59692f9a51848424231363643e0c16dd2a3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bf57d8b5547502d91f5550ca6ea0be16905604c90e61efb6741e5ec3ce607c7a65f0b31e1673c96c60a06a2f64f5239cab6e94d3a50095fb822ea9b1c1bb2f0a
|
|
7
|
+
data.tar.gz: 4b42aa5aa67b0e45bbbc8a9f29ca3a969efd8ade3b6dfca6cff082f526ec65a2a2e5c8fa17f512d33470b82535af8b675fc80903ccd75db26748e9845dd9a612
|
data/Changes.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
# connection_pool Changelog
|
|
2
|
+
|
|
3
|
+
2.4.1
|
|
4
|
+
------
|
|
5
|
+
|
|
6
|
+
- New `auto_reload_after_fork` config option to disable auto-drop [#177, shayonj]
|
|
7
|
+
|
|
8
|
+
2.4.0
|
|
9
|
+
------
|
|
10
|
+
|
|
11
|
+
- Automatically drop all connections after fork [#166]
|
|
12
|
+
|
|
13
|
+
2.3.0
|
|
14
|
+
------
|
|
15
|
+
|
|
16
|
+
- Minimum Ruby version is now 2.5.0
|
|
17
|
+
- Add pool size to TimeoutError message
|
|
18
|
+
|
|
19
|
+
2.2.5
|
|
20
|
+
------
|
|
21
|
+
|
|
22
|
+
- Fix argument forwarding on Ruby 2.7 [#149]
|
|
23
|
+
|
|
24
|
+
2.2.4
|
|
25
|
+
------
|
|
26
|
+
|
|
27
|
+
- Add `reload` to close all connections, recreating them afterwards [Andrew Marshall, #140]
|
|
28
|
+
- Add `then` as a way to use a pool or a bare connection with the same code path [#138]
|
|
29
|
+
|
|
30
|
+
2.2.3
|
|
31
|
+
------
|
|
32
|
+
|
|
33
|
+
- Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130]
|
|
34
|
+
- Use monotonic clock present in all modern Rubies [Tero Tasanen, #109]
|
|
35
|
+
- Remove code hacks necessary for JRuby 1.7
|
|
36
|
+
- Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113]
|
|
37
|
+
|
|
38
|
+
2.2.2
|
|
39
|
+
------
|
|
40
|
+
|
|
41
|
+
- Add pool `size` and `available` accessors for metrics and monitoring
|
|
42
|
+
purposes [#97, robholland]
|
|
3
43
|
|
|
4
44
|
2.2.1
|
|
5
45
|
------
|
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,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
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"
|
|
22
|
+
|
|
23
|
+
s.metadata = {"changelog_uri" => "https://github.com/mperham/connection_pool/blob/main/Changes.md", "rubygems_mfa_required" => "true"}
|
|
21
24
|
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,9 +15,10 @@ 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
|
|
21
|
+
attr_reader :max
|
|
31
22
|
|
|
32
23
|
##
|
|
33
24
|
# Creates a new pool with +size+ connections that are created from the given
|
|
@@ -38,8 +29,8 @@ class ConnectionPool::TimedStack
|
|
|
38
29
|
@created = 0
|
|
39
30
|
@que = []
|
|
40
31
|
@max = size
|
|
41
|
-
@mutex = Mutex.new
|
|
42
|
-
@resource = ConditionVariable.new
|
|
32
|
+
@mutex = Thread::Mutex.new
|
|
33
|
+
@resource = Thread::ConditionVariable.new
|
|
43
34
|
@shutdown_block = nil
|
|
44
35
|
end
|
|
45
36
|
|
|
@@ -63,7 +54,7 @@ class ConnectionPool::TimedStack
|
|
|
63
54
|
##
|
|
64
55
|
# Retrieves a connection from the stack. If a connection is available it is
|
|
65
56
|
# immediately returned. If no connection is available within the given
|
|
66
|
-
# timeout a
|
|
57
|
+
# timeout a ConnectionPool::TimeoutError is raised.
|
|
67
58
|
#
|
|
68
59
|
# +:timeout+ is the only checked entry in +options+ and is preferred over
|
|
69
60
|
# the +timeout+ argument (which will be removed in a future release). Other
|
|
@@ -73,7 +64,7 @@ class ConnectionPool::TimedStack
|
|
|
73
64
|
options, timeout = timeout, 0.5 if Hash === timeout
|
|
74
65
|
timeout = options.fetch :timeout, timeout
|
|
75
66
|
|
|
76
|
-
deadline =
|
|
67
|
+
deadline = current_time + timeout
|
|
77
68
|
@mutex.synchronize do
|
|
78
69
|
loop do
|
|
79
70
|
raise ConnectionPool::PoolShuttingDownError if @shutdown_block
|
|
@@ -82,25 +73,28 @@ class ConnectionPool::TimedStack
|
|
|
82
73
|
connection = try_create(options)
|
|
83
74
|
return connection if connection
|
|
84
75
|
|
|
85
|
-
to_wait = deadline -
|
|
86
|
-
raise
|
|
76
|
+
to_wait = deadline - current_time
|
|
77
|
+
raise ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0
|
|
87
78
|
@resource.wait(@mutex, to_wait)
|
|
88
79
|
end
|
|
89
80
|
end
|
|
90
81
|
end
|
|
91
82
|
|
|
92
83
|
##
|
|
93
|
-
# Shuts down the TimedStack
|
|
94
|
-
#
|
|
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+.
|
|
95
88
|
|
|
96
|
-
def shutdown(&block)
|
|
97
|
-
raise ArgumentError, "shutdown must receive a block" unless
|
|
89
|
+
def shutdown(reload: false, &block)
|
|
90
|
+
raise ArgumentError, "shutdown must receive a block" unless block
|
|
98
91
|
|
|
99
92
|
@mutex.synchronize do
|
|
100
93
|
@shutdown_block = block
|
|
101
94
|
@resource.broadcast
|
|
102
95
|
|
|
103
96
|
shutdown_connections
|
|
97
|
+
@shutdown_block = nil if reload
|
|
104
98
|
end
|
|
105
99
|
end
|
|
106
100
|
|
|
@@ -120,6 +114,10 @@ class ConnectionPool::TimedStack
|
|
|
120
114
|
|
|
121
115
|
private
|
|
122
116
|
|
|
117
|
+
def current_time
|
|
118
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
119
|
+
end
|
|
120
|
+
|
|
123
121
|
##
|
|
124
122
|
# This is an extension point for TimedStack and is called with a mutex.
|
|
125
123
|
#
|
|
@@ -148,6 +146,7 @@ class ConnectionPool::TimedStack
|
|
|
148
146
|
conn = fetch_connection(options)
|
|
149
147
|
@shutdown_block.call(conn)
|
|
150
148
|
end
|
|
149
|
+
@created = 0
|
|
151
150
|
end
|
|
152
151
|
|
|
153
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
|
|
@@ -30,32 +36,72 @@ require_relative 'connection_pool/timed_stack'
|
|
|
30
36
|
# Accepts the following options:
|
|
31
37
|
# - :size - number of connections to pool, defaults to 5
|
|
32
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
|
|
33
40
|
#
|
|
34
41
|
class ConnectionPool
|
|
35
|
-
DEFAULTS = {size: 5, timeout: 5}
|
|
36
|
-
|
|
37
|
-
class Error < RuntimeError
|
|
38
|
-
end
|
|
42
|
+
DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}
|
|
39
43
|
|
|
40
44
|
def self.wrap(options, &block)
|
|
41
45
|
Wrapper.new(options, &block)
|
|
42
46
|
end
|
|
43
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
|
+
|
|
44
90
|
def initialize(options = {}, &block)
|
|
45
|
-
raise ArgumentError,
|
|
91
|
+
raise ArgumentError, "Connection pool requires a block" unless block
|
|
46
92
|
|
|
47
93
|
options = DEFAULTS.merge(options)
|
|
48
94
|
|
|
49
|
-
@size = options.fetch(:size)
|
|
95
|
+
@size = Integer(options.fetch(:size))
|
|
50
96
|
@timeout = options.fetch(:timeout)
|
|
97
|
+
@auto_reload_after_fork = options.fetch(:auto_reload_after_fork)
|
|
51
98
|
|
|
52
99
|
@available = TimedStack.new(@size, &block)
|
|
53
|
-
@key = :"
|
|
100
|
+
@key = :"pool-#{@available.object_id}"
|
|
101
|
+
@key_count = :"pool-#{@available.object_id}-count"
|
|
102
|
+
INSTANCES[self] = self if INSTANCES
|
|
54
103
|
end
|
|
55
104
|
|
|
56
|
-
if Thread.respond_to?(:handle_interrupt)
|
|
57
|
-
|
|
58
|
-
# MRI
|
|
59
105
|
def with(options = {})
|
|
60
106
|
Thread.handle_interrupt(Exception => :never) do
|
|
61
107
|
conn = checkout(options)
|
|
@@ -68,81 +114,62 @@ if Thread.respond_to?(:handle_interrupt)
|
|
|
68
114
|
end
|
|
69
115
|
end
|
|
70
116
|
end
|
|
71
|
-
|
|
72
|
-
else
|
|
73
|
-
|
|
74
|
-
# jruby 1.7.x
|
|
75
|
-
def with(options = {})
|
|
76
|
-
conn = checkout(options)
|
|
77
|
-
begin
|
|
78
|
-
yield conn
|
|
79
|
-
ensure
|
|
80
|
-
checkin
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
end
|
|
117
|
+
alias_method :then, :with
|
|
85
118
|
|
|
86
119
|
def checkout(options = {})
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
@
|
|
120
|
+
if ::Thread.current[@key]
|
|
121
|
+
::Thread.current[@key_count] += 1
|
|
122
|
+
::Thread.current[@key]
|
|
90
123
|
else
|
|
91
|
-
|
|
124
|
+
::Thread.current[@key_count] = 1
|
|
125
|
+
::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
|
|
92
126
|
end
|
|
93
|
-
|
|
94
|
-
stack.push conn
|
|
95
|
-
conn
|
|
96
127
|
end
|
|
97
128
|
|
|
98
|
-
def checkin
|
|
99
|
-
|
|
100
|
-
|
|
129
|
+
def checkin(force: false)
|
|
130
|
+
if ::Thread.current[@key]
|
|
131
|
+
if ::Thread.current[@key_count] == 1 || force
|
|
132
|
+
@available.push(::Thread.current[@key])
|
|
133
|
+
::Thread.current[@key] = nil
|
|
134
|
+
::Thread.current[@key_count] = nil
|
|
135
|
+
else
|
|
136
|
+
::Thread.current[@key_count] -= 1
|
|
137
|
+
end
|
|
138
|
+
elsif !force
|
|
139
|
+
raise ConnectionPool::Error, "no connections are checked out"
|
|
140
|
+
end
|
|
101
141
|
|
|
102
142
|
nil
|
|
103
143
|
end
|
|
104
144
|
|
|
145
|
+
##
|
|
146
|
+
# Shuts down the ConnectionPool by passing each connection to +block+ and
|
|
147
|
+
# then removing it from the pool. Attempting to checkout a connection after
|
|
148
|
+
# shutdown will raise +ConnectionPool::PoolShuttingDownError+.
|
|
149
|
+
|
|
105
150
|
def shutdown(&block)
|
|
106
151
|
@available.shutdown(&block)
|
|
107
152
|
end
|
|
108
153
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
raise ConnectionPool::Error, 'no connections are checked out'
|
|
114
|
-
else
|
|
115
|
-
stack.pop
|
|
116
|
-
end
|
|
117
|
-
end
|
|
154
|
+
##
|
|
155
|
+
# Reloads the ConnectionPool by passing each connection to +block+ and then
|
|
156
|
+
# removing it the pool. Subsequent checkouts will create new connections as
|
|
157
|
+
# needed.
|
|
118
158
|
|
|
119
|
-
def
|
|
120
|
-
|
|
159
|
+
def reload(&block)
|
|
160
|
+
@available.shutdown(reload: true, &block)
|
|
121
161
|
end
|
|
122
162
|
|
|
123
|
-
|
|
124
|
-
|
|
163
|
+
# Size of this connection pool
|
|
164
|
+
attr_reader :size
|
|
165
|
+
# Automatically drop all connections after fork
|
|
166
|
+
attr_reader :auto_reload_after_fork
|
|
125
167
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def with(&block)
|
|
131
|
-
@pool.with(&block)
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def pool_shutdown(&block)
|
|
135
|
-
@pool.shutdown(&block)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def respond_to?(id, *args)
|
|
139
|
-
METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def method_missing(name, *args, &block)
|
|
143
|
-
with do |connection|
|
|
144
|
-
connection.send(name, *args, &block)
|
|
145
|
-
end
|
|
146
|
-
end
|
|
168
|
+
# Number of pool entries available for checkout at this instant.
|
|
169
|
+
def available
|
|
170
|
+
@available.length
|
|
147
171
|
end
|
|
148
172
|
end
|
|
173
|
+
|
|
174
|
+
require_relative "connection_pool/timed_stack"
|
|
175
|
+
require_relative "connection_pool/wrapper"
|