connection_pool 2.2.0 → 2.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 57159a2ea25da28e141dc80936a642134fbc9680
4
- data.tar.gz: 7f534538909e40c117b84090a89b6f4fcd5ca8ee
2
+ SHA256:
3
+ metadata.gz: '0993b72c233b027a1229d532a126b4a1623ef3f146a7b56502c539084f9a228d'
4
+ data.tar.gz: 274efa04fc445ca0044f62da29484a4ee9fb5e02b5f213fb873fedfd63cadff0
5
5
  SHA512:
6
- metadata.gz: bb15e188af8037b01d59ab3728ee010033ab6c71cdd82ed6a8f4f59609c23791d9afdb8a9e31c755079cd1ce7c6e0ca20a7b43d2bd4b142b7ddacbd9b9ad94c3
7
- data.tar.gz: 69e22e8e329f525fb875c2abe1090a35a63d035bb9337d9bd79604f46f1f1c3dbb92a83d6bf8d899388e8873cf04060c288bfef0e760e14b23a2fa12b1ffdf32
6
+ metadata.gz: 17c5bea167386115e8672b394138e0f66b55e6371b803dcfee7be08e51c84353aa9cd0257f169ae04053bc95be6b0c9b06576579f4915e68271146b1c9d602a5
7
+ data.tar.gz: 4eeccb9eaf397e8e386a41f81005b10d43d7441297661ab0d7656b6be30ac7ff1e751a95ed5c5b5a412124a29a0af3f5c90ae2882d70d3fa29d9dc8b2df412e3
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ continue-on-error: ${{ matrix.experimental }}
9
+ strategy:
10
+ fail-fast: false
11
+ matrix:
12
+ ruby: ["2.4", "2.5", "2.6", "2.7", "3.0", "jruby"]
13
+ experimental: [false]
14
+ include:
15
+ - ruby: "truffleruby"
16
+ experimental: true
17
+ steps:
18
+ - uses: actions/checkout@v2
19
+ - uses: ruby/setup-ruby@v1
20
+ with:
21
+ ruby-version: ${{matrix.ruby}}
22
+ bundler-cache: true
23
+
24
+ - name: Run tests
25
+ timeout-minutes: 5
26
+ run: ${{matrix.env}} bundle exec rake
data/Changes.md CHANGED
@@ -1,5 +1,35 @@
1
- connection\_pool changelog
2
- ---------------------------
1
+ # connection_pool Changelog
2
+
3
+ 2.2.5
4
+ ------
5
+
6
+ - Fix argument forwarding on Ruby 2.7 [#149]
7
+
8
+ 2.2.4
9
+ ------
10
+
11
+ - Add `reload` to close all connections, recreating them afterwards [Andrew Marshall, #140]
12
+ - Add `then` as a way to use a pool or a bare connection with the same code path [#138]
13
+
14
+ 2.2.3
15
+ ------
16
+
17
+ - Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130]
18
+ - Use monotonic clock present in all modern Rubies [Tero Tasanen, #109]
19
+ - Remove code hacks necessary for JRuby 1.7
20
+ - Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113]
21
+
22
+ 2.2.2
23
+ ------
24
+
25
+ - Add pool `size` and `available` accessors for metrics and monitoring
26
+ purposes [#97, robholland]
27
+
28
+ 2.2.1
29
+ ------
30
+
31
+ - Allow CP::Wrapper to use an existing pool [#87, etiennebarrie]
32
+ - Use monotonic time for more accurate timeouts [#84, jdantonio]
3
33
 
4
34
  2.2.0
5
35
  ------
data/README.md CHANGED
@@ -1,6 +1,6 @@
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
 
@@ -8,11 +8,6 @@ MongoDB has its own connection pool. ActiveRecord has its own connection pool.
8
8
  This is a generic connection pool that can be used with anything, e.g. Redis,
9
9
  Dalli and other Ruby network clients.
10
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
-
16
11
 
17
12
  Usage
18
13
  -----
@@ -36,6 +31,14 @@ If all the objects in the connection pool are in use, `with` will block
36
31
  until one becomes available. If no object is available within `:timeout` seconds,
37
32
  `with` will raise a `Timeout::Error`.
38
33
 
34
+ You can also use `ConnectionPool#then` to support _both_ a
35
+ connection pool and a raw client (requires Ruby 2.5+).
36
+
37
+ ```ruby
38
+ # Compatible with a raw Redis::Client, and ConnectionPool Redis
39
+ $redis.then { |r| r.set 'foo' 'bar' }
40
+ ```
41
+
39
42
  Optionally, you can specify a timeout override using the with-block semantics:
40
43
 
41
44
  ``` ruby
@@ -50,11 +53,13 @@ sections when a resource is not available, or conversely if you are comfortable
50
53
  blocking longer on a particular resource. This is not implemented in the below
51
54
  `ConnectionPool::Wrapper` class.
52
55
 
56
+ ## Migrating to a Connection Pool
57
+
53
58
  You can use `ConnectionPool::Wrapper` to wrap a single global connection,
54
- making it easier to port your connection code over time:
59
+ making it easier to migrate existing connection code over time:
55
60
 
56
61
  ``` ruby
57
- $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect }
62
+ $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
58
63
  $redis.sadd('foo', 1)
59
64
  $redis.smembers('foo')
60
65
  ```
@@ -74,38 +79,67 @@ end
74
79
  Once you've ported your entire system to use `with`, you can simply remove
75
80
  `Wrapper` and use the simpler and faster `ConnectionPool`.
76
81
 
82
+
83
+ ## Shutdown
84
+
77
85
  You can shut down a ConnectionPool instance once it should no longer be used.
78
86
  Further checkout attempts will immediately raise an error but existing checkouts
79
87
  will work.
80
88
 
81
89
  ```ruby
82
90
  cp = ConnectionPool.new { Redis.new }
83
- cp.shutdown { |conn| conn.close }
91
+ cp.shutdown { |conn| conn.quit }
84
92
  ```
85
93
 
86
94
  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
95
+ **Note that shutting down is completely optional**; Ruby's garbage collector will reclaim
88
96
  unreferenced pools under normal circumstances.
89
97
 
98
+ ## Reload
99
+
100
+ You can reload a ConnectionPool instance in the case it is desired to close all
101
+ connections to the pool and, unlike `shutdown`, afterwards recreate connections
102
+ so the pool may continue to be used. Reloading may be useful after forking the
103
+ process.
104
+
105
+ ```ruby
106
+ cp = ConnectionPool.new { Redis.new }
107
+ cp.reload { |conn| conn.quit }
108
+ cp.with { |conn| conn.get('some-count') }
109
+ ```
110
+
111
+ Like `shutdown`, this will block until all connections are checked in and
112
+ closed.
113
+
114
+ ## Current State
115
+
116
+ There are several methods that return information about a pool.
117
+
118
+ ```ruby
119
+ cp = ConnectionPool.new(size: 10) { Redis.new }
120
+ cp.size # => 10
121
+ cp.available # => 10
122
+
123
+ cp.with do |conn|
124
+ cp.size # => 10
125
+ cp.available # => 9
126
+ end
127
+ ```
90
128
 
91
129
  Notes
92
130
  -----
93
131
 
94
132
  - Connections are lazily created as needed.
95
133
  - 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
134
+ connections should be self-repairing. This is true of the Dalli and Redis
97
135
  clients.
98
-
99
-
100
- Install
101
- -------
102
-
103
- ```
104
- $ gem install connection_pool
105
- ```
136
+ - **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
137
+ occasional silent corruption and mysterious errors. The Timeout API is unsafe
138
+ and cannot be used correctly, ever. Use proper socket timeout options as
139
+ exposed by Net::HTTP, Redis, Dalli, etc.
106
140
 
107
141
 
108
142
  Author
109
143
  ------
110
144
 
111
- Mike Perham, [@mperham](https://twitter.com/mperham), <http://mikeperham.com>
145
+ Mike Perham, [@getajobmike](https://twitter.com/getajobmike), <https://www.mikeperham.com>
data/Rakefile CHANGED
@@ -1,6 +1,6 @@
1
- require 'bundler/gem_tasks'
1
+ require "bundler/gem_tasks"
2
2
 
3
- require 'rake/testtask'
3
+ require "rake/testtask"
4
4
  Rake::TestTask.new
5
5
 
6
- task :default => :test
6
+ task default: :test
@@ -1,21 +1,21 @@
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 = `git ls-files`.split("\n")
13
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
16
15
  s.require_paths = ["lib"]
17
16
  s.license = "MIT"
18
- s.add_development_dependency 'bundler'
19
- s.add_development_dependency 'minitest', '>= 5.0.0'
20
- s.add_development_dependency 'rake'
17
+ s.add_development_dependency "bundler"
18
+ s.add_development_dependency "minitest", ">= 5.0.0"
19
+ s.add_development_dependency "rake"
20
+ s.required_ruby_version = ">= 2.2.0"
21
21
  end
@@ -1,21 +1,25 @@
1
- require_relative 'connection_pool/version'
2
- require_relative 'connection_pool/timed_stack'
1
+ require "timeout"
2
+ require "connection_pool/version"
3
3
 
4
+ class ConnectionPool
5
+ class Error < ::RuntimeError; end
6
+ class PoolShuttingDownError < ::ConnectionPool::Error; end
7
+ class TimeoutError < ::Timeout::Error; end
8
+ end
4
9
 
5
- # Generic connection pool class for e.g. sharing a limited number of network connections
6
- # among many threads. Note: Connections are lazily created.
10
+ # Generic connection pool class for sharing a limited number of objects or network connections
11
+ # among many threads. Note: pool elements are lazily created.
7
12
  #
8
13
  # Example usage with block (faster):
9
14
  #
10
15
  # @pool = ConnectionPool.new { Redis.new }
11
- #
12
16
  # @pool.with do |redis|
13
17
  # redis.lpop('my-list') if redis.llen('my-list') > 0
14
18
  # end
15
19
  #
16
20
  # Using optional timeout override (for that single invocation)
17
21
  #
18
- # @pool.with(:timeout => 2.0) do |redis|
22
+ # @pool.with(timeout: 2.0) do |redis|
19
23
  # redis.lpop('my-list') if redis.llen('my-list') > 0
20
24
  # end
21
25
  #
@@ -34,28 +38,23 @@ require_relative 'connection_pool/timed_stack'
34
38
  class ConnectionPool
35
39
  DEFAULTS = {size: 5, timeout: 5}
36
40
 
37
- class Error < RuntimeError
38
- end
39
-
40
41
  def self.wrap(options, &block)
41
42
  Wrapper.new(options, &block)
42
43
  end
43
44
 
44
45
  def initialize(options = {}, &block)
45
- raise ArgumentError, 'Connection pool requires a block' unless block
46
+ raise ArgumentError, "Connection pool requires a block" unless block
46
47
 
47
48
  options = DEFAULTS.merge(options)
48
49
 
49
- @size = options.fetch(:size)
50
+ @size = Integer(options.fetch(:size))
50
51
  @timeout = options.fetch(:timeout)
51
52
 
52
53
  @available = TimedStack.new(@size, &block)
53
- @key = :"current-#{@available.object_id}"
54
+ @key = :"pool-#{@available.object_id}"
55
+ @key_count = :"pool-#{@available.object_id}-count"
54
56
  end
55
57
 
56
- if Thread.respond_to?(:handle_interrupt)
57
-
58
- # MRI
59
58
  def with(options = {})
60
59
  Thread.handle_interrupt(Exception => :never) do
61
60
  conn = checkout(options)
@@ -68,81 +67,60 @@ if Thread.respond_to?(:handle_interrupt)
68
67
  end
69
68
  end
70
69
  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
70
+ alias then with
85
71
 
86
72
  def checkout(options = {})
87
- conn = if stack.empty?
88
- timeout = options[:timeout] || @timeout
89
- @available.pop(timeout: timeout)
73
+ if ::Thread.current[@key]
74
+ ::Thread.current[@key_count] += 1
75
+ ::Thread.current[@key]
90
76
  else
91
- stack.last
77
+ ::Thread.current[@key_count] = 1
78
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
92
79
  end
93
-
94
- stack.push conn
95
- conn
96
80
  end
97
81
 
98
82
  def checkin
99
- conn = pop_connection # mutates stack, must be on its own line
100
- @available.push(conn) if stack.empty?
83
+ if ::Thread.current[@key]
84
+ if ::Thread.current[@key_count] == 1
85
+ @available.push(::Thread.current[@key])
86
+ ::Thread.current[@key] = nil
87
+ ::Thread.current[@key_count] = nil
88
+ else
89
+ ::Thread.current[@key_count] -= 1
90
+ end
91
+ else
92
+ raise ConnectionPool::Error, "no connections are checked out"
93
+ end
101
94
 
102
95
  nil
103
96
  end
104
97
 
98
+ ##
99
+ # Shuts down the ConnectionPool by passing each connection to +block+ and
100
+ # then removing it from the pool. Attempting to checkout a connection after
101
+ # shutdown will raise +ConnectionPool::PoolShuttingDownError+.
102
+
105
103
  def shutdown(&block)
106
104
  @available.shutdown(&block)
107
105
  end
108
106
 
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
107
+ ##
108
+ # Reloads the ConnectionPool by passing each connection to +block+ and then
109
+ # removing it the pool. Subsequent checkouts will create new connections as
110
+ # needed.
118
111
 
119
- def stack
120
- ::Thread.current[@key] ||= []
112
+ def reload(&block)
113
+ @available.shutdown(reload: true, &block)
121
114
  end
122
115
 
123
- class Wrapper < ::BasicObject
124
- METHODS = [:with, :pool_shutdown]
125
-
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
116
+ # Size of this connection pool
117
+ attr_reader :size
141
118
 
142
- def method_missing(name, *args, &block)
143
- with do |connection|
144
- connection.send(name, *args, &block)
145
- end
146
- end
119
+ # Number of pool entries available for checkout at this instant.
120
+ def available
121
+ @available.length
147
122
  end
148
123
  end
124
+
125
+ require "connection_pool/timed_stack"
126
+ require "connection_pool/wrapper"
@@ -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
@@ -57,12 +49,12 @@ class ConnectionPool::TimedStack
57
49
  @resource.broadcast
58
50
  end
59
51
  end
60
- alias_method :<<, :push
52
+ alias << push
61
53
 
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,18 +73,20 @@ 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" 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)
89
+ def shutdown(reload: false, &block)
96
90
  raise ArgumentError, "shutdown must receive a block" unless block_given?
97
91
 
98
92
  @mutex.synchronize do
@@ -100,6 +94,7 @@ class ConnectionPool::TimedStack
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
  ##