connection_pool 2.2.0 → 2.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 57159a2ea25da28e141dc80936a642134fbc9680
4
- data.tar.gz: 7f534538909e40c117b84090a89b6f4fcd5ca8ee
2
+ SHA256:
3
+ metadata.gz: ea0776fcb09a3cc48ef4ca03774399e20b09e51039d0c47c1e4cb3bac621c52b
4
+ data.tar.gz: b955d6b4e984259f20ae8cf6414f59692f9a51848424231363643e0c16dd2a3f
5
5
  SHA512:
6
- metadata.gz: bb15e188af8037b01d59ab3728ee010033ab6c71cdd82ed6a8f4f59609c23791d9afdb8a9e31c755079cd1ce7c6e0ca20a7b43d2bd4b142b7ddacbd9b9ad94c3
7
- data.tar.gz: 69e22e8e329f525fb875c2abe1090a35a63d035bb9337d9bd79604f46f1f1c3dbb92a83d6bf8d899388e8873cf04060c288bfef0e760e14b23a2fa12b1ffdf32
6
+ metadata.gz: bf57d8b5547502d91f5550ca6ea0be16905604c90e61efb6741e5ec3ce607c7a65f0b31e1673c96c60a06a2f64f5239cab6e94d3a50095fb822ea9b1c1bb2f0a
7
+ data.tar.gz: 4b42aa5aa67b0e45bbbc8a9f29ca3a969efd8ade3b6dfca6cff082f526ec65a2a2e5c8fa17f512d33470b82535af8b675fc80903ccd75db26748e9845dd9a612
data/Changes.md CHANGED
@@ -1,5 +1,51 @@
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]
43
+
44
+ 2.2.1
45
+ ------
46
+
47
+ - Allow CP::Wrapper to use an existing pool [#87, etiennebarrie]
48
+ - Use monotonic time for more accurate timeouts [#84, jdantonio]
3
49
 
4
50
  2.2.0
5
51
  ------
data/README.md CHANGED
@@ -1,24 +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
-
11
- **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
12
- occasional silent corruption and mysterious errors. The Timeout API is unsafe
13
- and cannot be used correctly, ever. Use proper socket timeout options as
14
- exposed by Net::HTTP, Redis, Dalli, etc.
15
-
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.
16
10
 
17
11
  Usage
18
12
  -----
19
13
 
20
- Create a pool of objects to share amongst the fibers or threads in your Ruby
21
- application:
14
+ Create a pool of objects to share amongst the fibers or threads in your Ruby application:
22
15
 
23
16
  ``` ruby
24
17
  $memcached = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new }
@@ -33,8 +26,17 @@ end
33
26
  ```
34
27
 
35
28
  If all the objects in the connection pool are in use, `with` will block
36
- until one becomes available. If no object is available within `:timeout` seconds,
37
- `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
+ ```
38
40
 
39
41
  Optionally, you can specify a timeout override using the with-block semantics:
40
42
 
@@ -45,24 +47,23 @@ end
45
47
  ```
46
48
 
47
49
  This will only modify the resource-get timeout for this particular
48
- invocation. This is useful if you want to fail-fast on certain non critical
49
- sections when a resource is not available, or conversely if you are comfortable
50
- blocking longer on a particular resource. This is not implemented in the below
51
- `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.
54
+
55
+ ## Migrating to a Connection Pool
52
56
 
53
- You can use `ConnectionPool::Wrapper` to wrap a single global connection,
54
- making it easier to port your 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:
55
58
 
56
59
  ``` ruby
57
- $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect }
60
+ $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
58
61
  $redis.sadd('foo', 1)
59
62
  $redis.smembers('foo')
60
63
  ```
61
64
 
62
- The wrapper uses `method_missing` to checkout a connection, run the requested
63
- method and then immediately check the connection back into the pool. It's
64
- **not** high-performance so you'll want to port your performance sensitive code
65
- 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.
66
67
 
67
68
  ``` ruby
68
69
  $redis.with do |conn|
@@ -71,41 +72,64 @@ $redis.with do |conn|
71
72
  end
72
73
  ```
73
74
 
74
- Once you've ported your entire system to use `with`, you can simply remove
75
- `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`.
76
+
77
+
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.close }
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.
89
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.
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
-
99
-
100
- Install
101
- -------
102
-
103
- ```
104
- $ gem install connection_pool
105
- ```
126
+ - **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
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
129
+ exposed by Net::HTTP, Redis, Dalli, etc.
106
130
 
107
131
 
108
132
  Author
109
133
  ------
110
134
 
111
- 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,12 +1,3 @@
1
- require 'thread'
2
- require 'timeout'
3
-
4
- ##
5
- # Raised when you attempt to retrieve a connection from a pool that has been
6
- # shut down.
7
-
8
- class ConnectionPool::PoolShuttingDownError < RuntimeError; end
9
-
10
1
  ##
11
2
  # The TimedStack manages a pool of homogeneous connections (or any resource
12
3
  # you wish to manage). Connections are created lazily up to a given maximum
@@ -24,9 +15,10 @@ class ConnectionPool::PoolShuttingDownError < RuntimeError; end
24
15
  #
25
16
  # conn = ts.pop
26
17
  # ts.pop timeout: 5
27
- # #=> raises Timeout::Error after 5 seconds
18
+ # #=> raises ConnectionPool::TimeoutError after 5 seconds
28
19
 
29
20
  class ConnectionPool::TimedStack
21
+ attr_reader :max
30
22
 
31
23
  ##
32
24
  # Creates a new pool with +size+ connections that are created from the given
@@ -37,8 +29,8 @@ class ConnectionPool::TimedStack
37
29
  @created = 0
38
30
  @que = []
39
31
  @max = size
40
- @mutex = Mutex.new
41
- @resource = ConditionVariable.new
32
+ @mutex = Thread::Mutex.new
33
+ @resource = Thread::ConditionVariable.new
42
34
  @shutdown_block = nil
43
35
  end
44
36
 
@@ -62,7 +54,7 @@ class ConnectionPool::TimedStack
62
54
  ##
63
55
  # Retrieves a connection from the stack. If a connection is available it is
64
56
  # immediately returned. If no connection is available within the given
65
- # timeout a Timeout::Error is raised.
57
+ # timeout a ConnectionPool::TimeoutError is raised.
66
58
  #
67
59
  # +:timeout+ is the only checked entry in +options+ and is preferred over
68
60
  # the +timeout+ argument (which will be removed in a future release). Other
@@ -72,7 +64,7 @@ class ConnectionPool::TimedStack
72
64
  options, timeout = timeout, 0.5 if Hash === timeout
73
65
  timeout = options.fetch :timeout, timeout
74
66
 
75
- deadline = Time.now + timeout
67
+ deadline = current_time + timeout
76
68
  @mutex.synchronize do
77
69
  loop do
78
70
  raise ConnectionPool::PoolShuttingDownError if @shutdown_block
@@ -81,25 +73,28 @@ class ConnectionPool::TimedStack
81
73
  connection = try_create(options)
82
74
  return connection if connection
83
75
 
84
- to_wait = deadline - Time.now
85
- 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
86
78
  @resource.wait(@mutex, to_wait)
87
79
  end
88
80
  end
89
81
  end
90
82
 
91
83
  ##
92
- # Shuts down the TimedStack which prevents connections from being checked
93
- # 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+.
94
88
 
95
- def shutdown(&block)
96
- 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
97
91
 
98
92
  @mutex.synchronize do
99
93
  @shutdown_block = block
100
94
  @resource.broadcast
101
95
 
102
96
  shutdown_connections
97
+ @shutdown_block = nil if reload
103
98
  end
104
99
  end
105
100
 
@@ -119,6 +114,10 @@ class ConnectionPool::TimedStack
119
114
 
120
115
  private
121
116
 
117
+ def current_time
118
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
119
+ end
120
+
122
121
  ##
123
122
  # This is an extension point for TimedStack and is called with a mutex.
124
123
  #
@@ -147,6 +146,7 @@ class ConnectionPool::TimedStack
147
146
  conn = fetch_connection(options)
148
147
  @shutdown_block.call(conn)
149
148
  end
149
+ @created = 0
150
150
  end
151
151
 
152
152
  ##
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.2.0"
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,21 +1,27 @@
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
15
21
  #
16
22
  # Using optional timeout override (for that single invocation)
17
23
  #
18
- # @pool.with(:timeout => 2.0) do |redis|
24
+ # @pool.with(timeout: 2.0) do |redis|
19
25
  # redis.lpop('my-list') if redis.llen('my-list') > 0
20
26
  # end
21
27
  #
@@ -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 = ::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"