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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 7f87d3feec72672723a367bfa62719c8b7af8a35
4
- data.tar.gz: 51632684b4d5b93d6de0215180dc0f4e8a2ab922
2
+ SHA256:
3
+ metadata.gz: ea0776fcb09a3cc48ef4ca03774399e20b09e51039d0c47c1e4cb3bac621c52b
4
+ data.tar.gz: b955d6b4e984259f20ae8cf6414f59692f9a51848424231363643e0c16dd2a3f
5
5
  SHA512:
6
- metadata.gz: b3912e67f7b3f01f6e89047a56eb1f08f80820f5a154d0acde2d05f977d7566a33eacce3cee792ce2a52485b3218b9d718e4bacb89aafa050791166fb8d6e337
7
- data.tar.gz: 0d2e0ee5ad0c23db00b32737b82f0693cb70e2f5de0f7a5afa03c5ae7ac22765d13b0073086940c12edf6b5a00605e8db1a1ddecc80d373417448ba67e0bcfca
6
+ metadata.gz: bf57d8b5547502d91f5550ca6ea0be16905604c90e61efb6741e5ec3ce607c7a65f0b31e1673c96c60a06a2f64f5239cab6e94d3a50095fb822ea9b1c1bb2f0a
7
+ data.tar.gz: 4b42aa5aa67b0e45bbbc8a9f29ca3a969efd8ade3b6dfca6cff082f526ec65a2a2e5c8fa17f512d33470b82535af8b675fc80903ccd75db26748e9845dd9a612
data/Changes.md CHANGED
@@ -1,5 +1,45 @@
1
- connection\_pool changelog
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
- [![Build Status](https://travis-ci.org/mperham/connection_pool.svg)](https://travis-ci.org/mperham/connection_pool)
3
+ [![Build Status](https://github.com/mperham/connection_pool/actions/workflows/ci.yml/badge.svg)](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. ActiveRecord has its own connection pool.
8
- This is a generic connection pool that can be used with anything, e.g. Redis,
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. If no object is available within `:timeout` seconds,
32
- `with` will raise a `Timeout::Error`.
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. This is useful if you want to fail-fast on certain non critical
44
- sections when a resource is not available, or conversely if you are comfortable
45
- blocking longer on a particular resource. This is not implemented in the below
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.connect }
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
- method and then immediately check the connection back into the pool. It's
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 { |conn| conn.quit }
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
- unreferenced pools under normal circumstances.
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. This is true of the Dalli and Redis
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. The Timeout API is unsafe
100
- and cannot be used correctly, ever. Use proper socket timeout options as
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, [@mperham](https://twitter.com/mperham), <http://mikeperham.com>
135
+ Mike Perham, [@getajobmike](https://twitter.com/getajobmike), <https://www.mikeperham.com>
@@ -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 = "connection_pool"
6
- s.version = ConnectionPool::VERSION
7
- s.platform = Gem::Platform::RUBY
8
- s.authors = ["Mike Perham", "Damian Janowski"]
9
- s.email = ["mperham@gmail.com", "damian@educabilia.com"]
10
- s.homepage = "https://github.com/mperham/connection_pool"
11
- s.description = s.summary = %q{Generic connection pool for Ruby}
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 = `git ls-files`.split("\n")
14
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
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 'bundler'
19
- s.add_development_dependency 'minitest', '>= 5.0.0'
20
- s.add_development_dependency 'rake'
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 Timeout::Error after 5 seconds
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 Timeout::Error is raised.
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 = ConnectionPool.monotonic_time + timeout
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 - ConnectionPool.monotonic_time
86
- raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0
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 which prevents connections from being checked
94
- # out. The +block+ is called once for each connection on the stack.
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 block_given?
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
  ##
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.2.1"
2
+ VERSION = "2.4.1"
3
3
  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
@@ -1,14 +1,20 @@
1
- require_relative 'connection_pool/version'
2
- require_relative 'connection_pool/timed_stack'
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 e.g. sharing a limited number of network connections
6
- # among many threads. Note: Connections are lazily created.
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, 'Connection pool requires a block' unless block
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 = :"current-#{@available.object_id}"
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
- conn = if stack.empty?
88
- timeout = options[:timeout] || @timeout
89
- @available.pop(timeout: timeout)
120
+ if ::Thread.current[@key]
121
+ ::Thread.current[@key_count] += 1
122
+ ::Thread.current[@key]
90
123
  else
91
- stack.last
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
- conn = pop_connection # mutates stack, must be on its own line
100
- @available.push(conn) if stack.empty?
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
- private
110
-
111
- def pop_connection
112
- if stack.empty?
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 stack
120
- ::Thread.current[@key] ||= []
159
+ def reload(&block)
160
+ @available.shutdown(reload: true, &block)
121
161
  end
122
162
 
123
- class Wrapper < ::BasicObject
124
- METHODS = [:with, :pool_shutdown]
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
- def initialize(options = {}, &block)
127
- @pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) }
128
- end
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"