connection_pool 2.2.2 → 2.2.5

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: cf8177bf07f6c8e68b13f28416f6ccc0c64803af
4
- data.tar.gz: ff9c836ce5576d4d0cb1edf8d858a761f982e437
2
+ SHA256:
3
+ metadata.gz: '0993b72c233b027a1229d532a126b4a1623ef3f146a7b56502c539084f9a228d'
4
+ data.tar.gz: 274efa04fc445ca0044f62da29484a4ee9fb5e02b5f213fb873fedfd63cadff0
5
5
  SHA512:
6
- metadata.gz: 73bda4d8d0b3cc9daef5c93bb98b7649d811bd5c8f467e25c3aaf7d3353541225e7b30e1efd481a14e45f5c56b5fc846d49dabdd9a167702a71c4a9631a9b6a2
7
- data.tar.gz: a331275f67b3635e09792e646113e32bdc74254a2cdff032e972c6f2ebedd6391a1e508c36be7c4e33b40e2a07efe029a2ad0267c3ba4dc20b22813df86427bb
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,23 @@
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]
3
21
 
4
22
  2.2.2
5
23
  ------
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
 
@@ -31,6 +31,14 @@ If all the objects in the connection pool are in use, `with` will block
31
31
  until one becomes available. If no object is available within `:timeout` seconds,
32
32
  `with` will raise a `Timeout::Error`.
33
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
+
34
42
  Optionally, you can specify a timeout override using the with-block semantics:
35
43
 
36
44
  ``` ruby
@@ -51,7 +59,7 @@ You can use `ConnectionPool::Wrapper` to wrap a single global connection,
51
59
  making it easier to migrate existing connection code over time:
52
60
 
53
61
  ``` ruby
54
- $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect }
62
+ $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
55
63
  $redis.sadd('foo', 1)
56
64
  $redis.smembers('foo')
57
65
  ```
@@ -87,21 +95,51 @@ Shutting down a connection pool will block until all connections are checked in
87
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
136
  - **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
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
101
139
  exposed by Net::HTTP, Redis, Dalli, etc.
102
140
 
103
141
 
104
142
  Author
105
143
  ------
106
144
 
107
- 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,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 Timeout::Error after 5 seconds
18
+ # #=> raises ConnectionPool::TimeoutError after 5 seconds
29
19
 
30
20
  class ConnectionPool::TimedStack
31
21
  attr_reader :max
@@ -59,12 +49,12 @@ class ConnectionPool::TimedStack
59
49
  @resource.broadcast
60
50
  end
61
51
  end
62
- alias_method :<<, :push
52
+ alias << push
63
53
 
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 Timeout::Error is raised.
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 = ConnectionPool.monotonic_time + timeout
67
+ deadline = current_time + timeout
78
68
  @mutex.synchronize do
79
69
  loop do
80
70
  raise ConnectionPool::PoolShuttingDownError if @shutdown_block
@@ -83,18 +73,20 @@ class ConnectionPool::TimedStack
83
73
  connection = try_create(options)
84
74
  return connection if connection
85
75
 
86
- to_wait = deadline - ConnectionPool.monotonic_time
87
- 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
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 which prevents connections from being checked
95
- # 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+.
96
88
 
97
- def shutdown(&block)
89
+ def shutdown(reload: false, &block)
98
90
  raise ArgumentError, "shutdown must receive a block" unless block_given?
99
91
 
100
92
  @mutex.synchronize do
@@ -102,6 +94,7 @@ class ConnectionPool::TimedStack
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
  ##
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.2.2"
2
+ VERSION = "2.2.5"
3
3
  end
@@ -0,0 +1,57 @@
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/MethodMissingSuper
34
+ # rubocop:disable Style/MissingRespondToMissing
35
+ if ::RUBY_VERSION >= "3.0.0"
36
+ def method_missing(name, *args, **kwargs, &block)
37
+ with do |connection|
38
+ connection.send(name, *args, **kwargs, &block)
39
+ end
40
+ end
41
+ elsif ::RUBY_VERSION >= "2.7.0"
42
+ ruby2_keywords def method_missing(name, *args, &block)
43
+ with do |connection|
44
+ connection.send(name, *args, &block)
45
+ end
46
+ end
47
+ else
48
+ def method_missing(name, *args, &block)
49
+ with do |connection|
50
+ connection.send(name, *args, &block)
51
+ end
52
+ end
53
+ end
54
+ # rubocop:enable Style/MethodMissingSuper
55
+ # rubocop:enable Style/MissingRespondToMissing
56
+ end
57
+ end
@@ -1,14 +1,18 @@
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
@@ -34,29 +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_count = :"current-#{@available.object_id}-count"
54
+ @key = :"pool-#{@available.object_id}"
55
+ @key_count = :"pool-#{@available.object_id}-count"
55
56
  end
56
57
 
57
- if Thread.respond_to?(:handle_interrupt)
58
-
59
- # MRI
60
58
  def with(options = {})
61
59
  Thread.handle_interrupt(Exception => :never) do
62
60
  conn = checkout(options)
@@ -69,28 +67,15 @@ if Thread.respond_to?(:handle_interrupt)
69
67
  end
70
68
  end
71
69
  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
70
+ alias then with
86
71
 
87
72
  def checkout(options = {})
88
73
  if ::Thread.current[@key]
89
- ::Thread.current[@key_count]+= 1
74
+ ::Thread.current[@key_count] += 1
90
75
  ::Thread.current[@key]
91
76
  else
92
- ::Thread.current[@key_count]= 1
93
- ::Thread.current[@key]= @available.pop(options[:timeout] || @timeout)
77
+ ::Thread.current[@key_count] = 1
78
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
94
79
  end
95
80
  end
96
81
 
@@ -98,64 +83,44 @@ end
98
83
  if ::Thread.current[@key]
99
84
  if ::Thread.current[@key_count] == 1
100
85
  @available.push(::Thread.current[@key])
101
- ::Thread.current[@key]= nil
86
+ ::Thread.current[@key] = nil
87
+ ::Thread.current[@key_count] = nil
102
88
  else
103
- ::Thread.current[@key_count]-= 1
89
+ ::Thread.current[@key_count] -= 1
104
90
  end
105
91
  else
106
- raise ConnectionPool::Error, 'no connections are checked out'
92
+ raise ConnectionPool::Error, "no connections are checked out"
107
93
  end
108
94
 
109
95
  nil
110
96
  end
111
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
+
112
103
  def shutdown(&block)
113
104
  @available.shutdown(&block)
114
105
  end
115
106
 
116
- # Size of this connection pool
117
- def size
118
- @size
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.
111
+
112
+ def reload(&block)
113
+ @available.shutdown(reload: true, &block)
119
114
  end
120
115
 
116
+ # Size of this connection pool
117
+ attr_reader :size
118
+
121
119
  # Number of pool entries available for checkout at this instant.
122
120
  def available
123
121
  @available.length
124
122
  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
123
  end
124
+
125
+ require "connection_pool/timed_stack"
126
+ require "connection_pool/wrapper"
data/test/helper.rb CHANGED
@@ -1,8 +1,8 @@
1
- gem 'minitest'
1
+ gem "minitest"
2
2
 
3
- require 'minitest/pride'
4
- require 'minitest/autorun'
3
+ require "minitest/pride"
4
+ require "minitest/autorun"
5
5
 
6
6
  $VERBOSE = 1
7
7
 
8
- require_relative '../lib/connection_pool'
8
+ require_relative "../lib/connection_pool"
@@ -1,7 +1,6 @@
1
- require_relative 'helper'
1
+ require_relative "helper"
2
2
 
3
3
  class TestConnectionPool < Minitest::Test
4
-
5
4
  class NetworkConnection
6
5
  SLEEP_TIME = 0.1
7
6
 
@@ -9,8 +8,14 @@ class TestConnectionPool < Minitest::Test
9
8
  @x = 0
10
9
  end
11
10
 
12
- def do_something
13
- @x += 1
11
+ def do_something(*_args, increment: 1)
12
+ @x += increment
13
+ sleep SLEEP_TIME
14
+ @x
15
+ end
16
+
17
+ def do_something_with_positional_hash(options)
18
+ @x += options[:increment] || 1
14
19
  sleep SLEEP_TIME
15
20
  @x
16
21
  end
@@ -43,12 +48,12 @@ class TestConnectionPool < Minitest::Test
43
48
  end
44
49
 
45
50
  def use_pool(pool, size)
46
- Array.new(size) do
51
+ Array.new(size) {
47
52
  Thread.new do
48
- pool.with do sleep end
53
+ pool.with { sleep }
49
54
  end
50
- end.each do |thread|
51
- Thread.pass until thread.status == 'sleep'
55
+ }.each do |thread|
56
+ Thread.pass until thread.status == "sleep"
52
57
  end
53
58
  end
54
59
 
@@ -67,13 +72,13 @@ class TestConnectionPool < Minitest::Test
67
72
 
68
73
  generations = 3
69
74
 
70
- result = Array.new(pool_size * generations) do
75
+ result = Array.new(pool_size * generations) {
71
76
  Thread.new do
72
77
  pool.with do |net|
73
78
  net.do_something
74
79
  end
75
80
  end
76
- end.map(&:value)
81
+ }.map(&:value)
77
82
 
78
83
  finish = Time.new
79
84
 
@@ -84,14 +89,14 @@ class TestConnectionPool < Minitest::Test
84
89
 
85
90
  def test_timeout
86
91
  pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
87
- thread = Thread.new do
92
+ thread = Thread.new {
88
93
  pool.with do |net|
89
94
  net.do_something
90
95
  sleep 0.01
91
96
  end
92
- end
97
+ }
93
98
 
94
- Thread.pass while thread.status == 'run'
99
+ Thread.pass while thread.status == "run"
95
100
 
96
101
  assert_raises Timeout::Error do
97
102
  pool.with { |net| net.do_something }
@@ -108,31 +113,46 @@ class TestConnectionPool < Minitest::Test
108
113
  pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
109
114
 
110
115
  pool.with do
111
- assert_raises Timeout::Error do
112
- Thread.new { pool.checkout }.join
113
- end
116
+ Thread.new {
117
+ assert_raises Timeout::Error do
118
+ pool.checkout
119
+ end
120
+ }.join
114
121
  end
115
122
 
116
123
  assert Thread.new { pool.checkout }.join
117
124
  end
118
125
 
126
+ def test_then
127
+ pool = ConnectionPool.new { Object.new }
128
+
129
+ assert_equal pool.method(:then), pool.method(:with)
130
+ end
131
+
119
132
  def test_with_timeout
120
133
  pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
121
134
 
122
135
  assert_raises Timeout::Error do
123
136
  Timeout.timeout(0.01) do
124
137
  pool.with do |obj|
125
- assert_equal 0, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
138
+ assert_equal 0, pool.available
126
139
  sleep 0.015
127
140
  end
128
141
  end
129
142
  end
130
- assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
143
+ assert_equal 1, pool.available
131
144
  end
132
145
 
133
- def test_checkout_ignores_timeout
134
- skip("Thread.handle_interrupt not available") unless Thread.respond_to?(:handle_interrupt)
146
+ def test_invalid_size
147
+ assert_raises ArgumentError, TypeError do
148
+ ConnectionPool.new(timeout: 0, size: nil) { Object.new }
149
+ end
150
+ assert_raises ArgumentError, TypeError do
151
+ ConnectionPool.new(timeout: 0, size: "") { Object.new }
152
+ end
153
+ end
135
154
 
155
+ def test_handle_interrupt_ensures_checkin
136
156
  pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
137
157
  def pool.checkout(options)
138
158
  sleep 0.015
@@ -140,7 +160,8 @@ class TestConnectionPool < Minitest::Test
140
160
  end
141
161
 
142
162
  did_something = false
143
- assert_raises Timeout::Error do
163
+
164
+ action = lambda do
144
165
  Timeout.timeout(0.01) do
145
166
  pool.with do |obj|
146
167
  did_something = true
@@ -152,18 +173,33 @@ class TestConnectionPool < Minitest::Test
152
173
  end
153
174
  end
154
175
  end
155
- assert did_something
156
- assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size
176
+
177
+ if RUBY_ENGINE == "ruby"
178
+ # These asserts rely on the Ruby implementation reaching `did_something =
179
+ # true` before the interrupt is detected by the thread. Interrupt
180
+ # detection timing is implementation-specific in practice, with JRuby,
181
+ # Rubinius, and TruffleRuby all having different interrupt timings to MRI.
182
+ # In fact they generally detect interrupts more quickly than MRI, so they
183
+ # may not reach `did_something = true` before detecting the interrupt.
184
+
185
+ assert_raises Timeout::Error, &action
186
+
187
+ assert did_something
188
+ else
189
+ action.call
190
+ end
191
+
192
+ assert_equal 1, pool.available
157
193
  end
158
194
 
159
195
  def test_explicit_return
160
- pool = ConnectionPool.new(timeout: 0, size: 1) do
196
+ pool = ConnectionPool.new(timeout: 0, size: 1) {
161
197
  mock = Minitest::Mock.new
162
198
  def mock.disconnect!
163
199
  raise "should not disconnect upon explicit return"
164
200
  end
165
201
  mock
166
- end
202
+ }
167
203
 
168
204
  pool.with do |conn|
169
205
  return true
@@ -173,14 +209,14 @@ class TestConnectionPool < Minitest::Test
173
209
  def test_with_timeout_override
174
210
  pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
175
211
 
176
- t = Thread.new do
212
+ t = Thread.new {
177
213
  pool.with do |net|
178
214
  net.do_something
179
215
  sleep 0.01
180
216
  end
181
- end
217
+ }
182
218
 
183
- Thread.pass while t.status == 'run'
219
+ Thread.pass while t.status == "run"
184
220
 
185
221
  assert_raises Timeout::Error do
186
222
  pool.with { |net| net.do_something }
@@ -195,9 +231,11 @@ class TestConnectionPool < Minitest::Test
195
231
  pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
196
232
  conn = pool.checkout
197
233
 
198
- assert_raises Timeout::Error do
199
- Thread.new { pool.checkout }.join
200
- end
234
+ Thread.new {
235
+ assert_raises Timeout::Error do
236
+ pool.checkout
237
+ end
238
+ }.join
201
239
 
202
240
  pool.checkin
203
241
 
@@ -206,17 +244,14 @@ class TestConnectionPool < Minitest::Test
206
244
 
207
245
  def test_returns_value
208
246
  pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
209
- assert_equal 1, pool.with {|o| 1 }
247
+ assert_equal 1, pool.with { |o| 1 }
210
248
  end
211
249
 
212
250
  def test_checkin_never_checkout
213
251
  pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
214
252
 
215
- e = assert_raises ConnectionPool::Error do
216
- pool.checkin
217
- end
218
-
219
- assert_equal 'no connections are checked out', e.message
253
+ e = assert_raises(ConnectionPool::Error) { pool.checkin }
254
+ assert_equal "no connections are checked out", e.message
220
255
  end
221
256
 
222
257
  def test_checkin_no_current_checkout
@@ -238,11 +273,11 @@ class TestConnectionPool < Minitest::Test
238
273
 
239
274
  pool.checkin
240
275
 
241
- assert_raises Timeout::Error do
242
- Thread.new do
276
+ Thread.new {
277
+ assert_raises Timeout::Error do
243
278
  pool.checkout
244
- end.join
245
- end
279
+ end
280
+ }.join
246
281
 
247
282
  pool.checkin
248
283
 
@@ -263,9 +298,9 @@ class TestConnectionPool < Minitest::Test
263
298
  pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
264
299
  conn = pool.checkout
265
300
 
266
- t = Thread.new do
301
+ t = Thread.new {
267
302
  pool.checkout
268
- end
303
+ }
269
304
 
270
305
  refute_same conn, t.value
271
306
  end
@@ -281,14 +316,14 @@ class TestConnectionPool < Minitest::Test
281
316
  def test_checkout_timeout_override
282
317
  pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
283
318
 
284
- thread = Thread.new do
319
+ thread = Thread.new {
285
320
  pool.with do |net|
286
321
  net.do_something
287
322
  sleep 0.01
288
323
  end
289
- end
324
+ }
290
325
 
291
- Thread.pass while thread.status == 'run'
326
+ Thread.pass while thread.status == "run"
292
327
 
293
328
  assert_raises Timeout::Error do
294
329
  pool.checkout
@@ -303,6 +338,8 @@ class TestConnectionPool < Minitest::Test
303
338
  assert_equal 2, pool.do_something
304
339
  assert_equal 5, pool.do_something_with_block { 3 }
305
340
  assert_equal 6, pool.with { |net| net.fast }
341
+ assert_equal 8, pool.do_something(increment: 2)
342
+ assert_equal 10, pool.do_something_with_positional_hash({ increment: 2, symbol_key: 3, "string_key" => 4 })
306
343
  end
307
344
 
308
345
  def test_passthru_respond_to
@@ -315,22 +352,22 @@ class TestConnectionPool < Minitest::Test
315
352
 
316
353
  def test_return_value
317
354
  pool = ConnectionPool.new(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
318
- result = pool.with do |net|
355
+ result = pool.with { |net|
319
356
  net.fast
320
- end
357
+ }
321
358
  assert_equal 1, result
322
359
  end
323
360
 
324
361
  def test_heavy_threading
325
362
  pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new }
326
363
 
327
- threads = Array.new(20) do
364
+ threads = Array.new(20) {
328
365
  Thread.new do
329
366
  pool.with do |net|
330
367
  sleep 0.01
331
368
  end
332
369
  end
333
- end
370
+ }
334
371
 
335
372
  threads.map { |thread| thread.join }
336
373
  end
@@ -338,9 +375,9 @@ class TestConnectionPool < Minitest::Test
338
375
  def test_reuses_objects_when_pool_not_saturated
339
376
  pool = ConnectionPool.new(size: 5) { NetworkConnection.new }
340
377
 
341
- ids = 10.times.map do
378
+ ids = 10.times.map {
342
379
  pool.with { |c| c.object_id }
343
- end
380
+ }
344
381
 
345
382
  assert_equal 1, ids.uniq.size
346
383
  end
@@ -349,32 +386,32 @@ class TestConnectionPool < Minitest::Test
349
386
  recorder = Recorder.new
350
387
  pool = ConnectionPool.new(size: 1) { recorder }
351
388
  pool.with do |r_outer|
352
- @other = Thread.new do |t|
389
+ @other = Thread.new { |t|
353
390
  pool.with do |r_other|
354
- r_other.do_work('other')
391
+ r_other.do_work("other")
355
392
  end
356
- end
393
+ }
357
394
 
358
395
  pool.with do |r_inner|
359
- r_inner.do_work('inner')
396
+ r_inner.do_work("inner")
360
397
  end
361
398
 
362
399
  Thread.pass
363
400
 
364
- r_outer.do_work('outer')
401
+ r_outer.do_work("outer")
365
402
  end
366
403
 
367
404
  @other.join
368
405
 
369
- assert_equal ['inner', 'outer', 'other'], recorder.calls
406
+ assert_equal ["inner", "outer", "other"], recorder.calls
370
407
  end
371
408
 
372
409
  def test_shutdown_is_executed_for_all_connections
373
410
  recorders = []
374
411
 
375
- pool = ConnectionPool.new(size: 3) do
412
+ pool = ConnectionPool.new(size: 3) {
376
413
  Recorder.new.tap { |r| recorders << r }
377
- end
414
+ }
378
415
 
379
416
  threads = use_pool pool, 3
380
417
 
@@ -390,7 +427,7 @@ class TestConnectionPool < Minitest::Test
390
427
  def test_raises_error_after_shutting_down
391
428
  pool = ConnectionPool.new(size: 1) { true }
392
429
 
393
- pool.shutdown { }
430
+ pool.shutdown {}
394
431
 
395
432
  assert_raises ConnectionPool::PoolShuttingDownError do
396
433
  pool.checkout
@@ -400,9 +437,9 @@ class TestConnectionPool < Minitest::Test
400
437
  def test_runs_shutdown_block_asynchronously_if_connection_was_in_use
401
438
  recorders = []
402
439
 
403
- pool = ConnectionPool.new(size: 3) do
440
+ pool = ConnectionPool.new(size: 3) {
404
441
  Recorder.new.tap { |r| recorders << r }
405
- end
442
+ }
406
443
 
407
444
  threads = use_pool pool, 2
408
445
 
@@ -422,7 +459,7 @@ class TestConnectionPool < Minitest::Test
422
459
  end
423
460
 
424
461
  def test_raises_an_error_if_shutdown_is_called_without_a_block
425
- pool = ConnectionPool.new(size: 1) { }
462
+ pool = ConnectionPool.new(size: 1) {}
426
463
 
427
464
  assert_raises ArgumentError do
428
465
  pool.shutdown
@@ -432,9 +469,9 @@ class TestConnectionPool < Minitest::Test
432
469
  def test_shutdown_is_executed_for_all_connections_in_wrapped_pool
433
470
  recorders = []
434
471
 
435
- wrapper = ConnectionPool::Wrapper.new(size: 3) do
472
+ wrapper = ConnectionPool::Wrapper.new(size: 3) {
436
473
  Recorder.new.tap { |r| recorders << r }
437
- end
474
+ }
438
475
 
439
476
  threads = use_pool wrapper, 3
440
477
 
@@ -447,6 +484,11 @@ class TestConnectionPool < Minitest::Test
447
484
  assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
448
485
  end
449
486
 
487
+ def test_wrapper_wrapped_pool
488
+ wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
489
+ assert_equal ConnectionPool, wrapper.wrapped_pool.class
490
+ end
491
+
450
492
  def test_wrapper_method_missing
451
493
  wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
452
494
 
@@ -466,14 +508,14 @@ class TestConnectionPool < Minitest::Test
466
508
  wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { Object.new }
467
509
 
468
510
  wrapper.with do
469
- assert_raises Timeout::Error do
470
- Thread.new do
471
- wrapper.with { flunk 'connection checked out :(' }
472
- end.join
473
- end
511
+ Thread.new {
512
+ assert_raises Timeout::Error do
513
+ wrapper.with { flunk "connection checked out :(" }
514
+ end
515
+ }.join
474
516
  end
475
517
 
476
- assert Thread.new { wrapper.with { } }.join
518
+ assert Thread.new { wrapper.with {} }.join
477
519
  end
478
520
 
479
521
  class ConnWithEval
@@ -493,10 +535,10 @@ class TestConnectionPool < Minitest::Test
493
535
  pool = ConnectionPool.new(size: 1) { recorder }
494
536
  wrapper = ConnectionPool::Wrapper.new(pool: pool)
495
537
 
496
- pool.with { |r| r.do_work('with') }
497
- wrapper.do_work('wrapped')
538
+ pool.with { |r| r.do_work("with") }
539
+ wrapper.do_work("wrapped")
498
540
 
499
- assert_equal ['with', 'wrapped'], recorder.calls
541
+ assert_equal ["with", "wrapped"], recorder.calls
500
542
  end
501
543
 
502
544
  def test_stats_without_active_connection
@@ -513,4 +555,13 @@ class TestConnectionPool < Minitest::Test
513
555
  assert_equal(1, pool.available)
514
556
  end
515
557
  end
558
+
559
+ def test_stats_with_string_size
560
+ pool = ConnectionPool.new(size: "2") { NetworkConnection.new }
561
+
562
+ pool.with do
563
+ assert_equal(2, pool.size)
564
+ assert_equal(1, pool.available)
565
+ end
566
+ end
516
567
  end
@@ -1,7 +1,6 @@
1
- require_relative 'helper'
1
+ require_relative "helper"
2
2
 
3
3
  class TestConnectionPoolTimedStack < Minitest::Test
4
-
5
4
  def setup
6
5
  @stack = ConnectionPool::TimedStack.new { Object.new }
7
6
  end
@@ -35,18 +34,18 @@ class TestConnectionPoolTimedStack < Minitest::Test
35
34
  end
36
35
 
37
36
  def test_object_creation_fails
38
- stack = ConnectionPool::TimedStack.new(2) { raise 'failure' }
37
+ stack = ConnectionPool::TimedStack.new(2) { raise "failure" }
39
38
 
40
39
  begin
41
40
  stack.pop
42
41
  rescue => error
43
- assert_equal 'failure', error.message
42
+ assert_equal "failure", error.message
44
43
  end
45
44
 
46
45
  begin
47
46
  stack.pop
48
47
  rescue => error
49
- assert_equal 'failure', error.message
48
+ assert_equal "failure", error.message
50
49
  end
51
50
 
52
51
  refute_empty stack
@@ -63,19 +62,13 @@ class TestConnectionPoolTimedStack < Minitest::Test
63
62
  end
64
63
 
65
64
  def test_pop_empty
66
- e = assert_raises Timeout::Error do
67
- @stack.pop timeout: 0
68
- end
69
-
70
- assert_equal 'Waited 0 sec', e.message
65
+ e = assert_raises(ConnectionPool::TimeoutError) { @stack.pop timeout: 0 }
66
+ assert_equal "Waited 0 sec", e.message
71
67
  end
72
68
 
73
69
  def test_pop_empty_2_0_compatibility
74
- e = assert_raises Timeout::Error do
75
- @stack.pop 0
76
- end
77
-
78
- assert_equal 'Waited 0 sec', e.message
70
+ e = assert_raises(Timeout::Error) { @stack.pop 0 }
71
+ assert_equal "Waited 0 sec", e.message
79
72
  end
80
73
 
81
74
  def test_pop_full
@@ -88,11 +81,11 @@ class TestConnectionPoolTimedStack < Minitest::Test
88
81
  end
89
82
 
90
83
  def test_pop_wait
91
- thread = Thread.start do
84
+ thread = Thread.start {
92
85
  @stack.pop
93
- end
86
+ }
94
87
 
95
- Thread.pass while thread.status == 'run'
88
+ Thread.pass while thread.status == "run"
96
89
 
97
90
  object = Object.new
98
91
 
@@ -102,13 +95,23 @@ class TestConnectionPoolTimedStack < Minitest::Test
102
95
  end
103
96
 
104
97
  def test_pop_shutdown
105
- @stack.shutdown { }
98
+ @stack.shutdown {}
106
99
 
107
100
  assert_raises ConnectionPool::PoolShuttingDownError do
108
101
  @stack.pop
109
102
  end
110
103
  end
111
104
 
105
+ def test_pop_shutdown_reload
106
+ stack = ConnectionPool::TimedStack.new(1) { Object.new }
107
+ object = stack.pop
108
+ stack.push(object)
109
+
110
+ stack.shutdown(reload: true) {}
111
+
112
+ refute_equal object, stack.pop
113
+ end
114
+
112
115
  def test_push
113
116
  stack = ConnectionPool::TimedStack.new(1) { Object.new }
114
117
 
@@ -144,6 +147,4 @@ class TestConnectionPoolTimedStack < Minitest::Test
144
147
  refute_empty called
145
148
  assert_empty @stack
146
149
  end
147
-
148
150
  end
149
-
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: connection_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.2
4
+ version: 2.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
8
8
  - Damian Janowski
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-05-24 00:00:00.000000000 Z
12
+ date: 2021-04-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -61,8 +61,8 @@ executables: []
61
61
  extensions: []
62
62
  extra_rdoc_files: []
63
63
  files:
64
+ - ".github/workflows/ci.yml"
64
65
  - ".gitignore"
65
- - ".travis.yml"
66
66
  - Changes.md
67
67
  - Gemfile
68
68
  - LICENSE
@@ -70,9 +70,9 @@ files:
70
70
  - Rakefile
71
71
  - connection_pool.gemspec
72
72
  - lib/connection_pool.rb
73
- - lib/connection_pool/monotonic_time.rb
74
73
  - lib/connection_pool/timed_stack.rb
75
74
  - lib/connection_pool/version.rb
75
+ - lib/connection_pool/wrapper.rb
76
76
  - test/helper.rb
77
77
  - test/test_connection_pool.rb
78
78
  - test/test_connection_pool_timed_stack.rb
@@ -80,7 +80,7 @@ homepage: https://github.com/mperham/connection_pool
80
80
  licenses:
81
81
  - MIT
82
82
  metadata: {}
83
- post_install_message:
83
+ post_install_message:
84
84
  rdoc_options: []
85
85
  require_paths:
86
86
  - lib
@@ -88,16 +88,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
88
88
  requirements:
89
89
  - - ">="
90
90
  - !ruby/object:Gem::Version
91
- version: '0'
91
+ version: 2.2.0
92
92
  required_rubygems_version: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  requirements: []
98
- rubyforge_project:
99
- rubygems_version: 2.6.13
100
- signing_key:
98
+ rubygems_version: 3.1.4
99
+ signing_key:
101
100
  specification_version: 4
102
101
  summary: Generic connection pool for Ruby
103
102
  test_files:
data/.travis.yml DELETED
@@ -1,10 +0,0 @@
1
- ---
2
- sudo: false
3
- cache: bundler
4
- language: ruby
5
- rvm:
6
- - 2.2.9
7
- - 2.3.6
8
- - 2.4.3
9
- - 2.5.0
10
- - jruby
@@ -1,66 +0,0 @@
1
- # Global monotonic clock from Concurrent Ruby 1.0.
2
- # Copyright (c) Jerry D'Antonio -- released under the MIT license.
3
- # Slightly modified; used with permission.
4
- # https://github.com/ruby-concurrency/concurrent-ruby
5
-
6
- require 'thread'
7
-
8
- class ConnectionPool
9
-
10
- class_definition = Class.new do
11
-
12
- if defined?(Process::CLOCK_MONOTONIC)
13
-
14
- # @!visibility private
15
- def get_time
16
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
17
- end
18
-
19
- elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby'
20
-
21
- # @!visibility private
22
- def get_time
23
- java.lang.System.nanoTime() / 1_000_000_000.0
24
- end
25
-
26
- else
27
-
28
- # @!visibility private
29
- def initialize
30
- @mutex = Mutex.new
31
- @last_time = Time.now.to_f
32
- end
33
-
34
- # @!visibility private
35
- def get_time
36
- @mutex.synchronize do
37
- now = Time.now.to_f
38
- if @last_time < now
39
- @last_time = now
40
- else # clock has moved back in time
41
- @last_time += 0.000_001
42
- end
43
- end
44
- end
45
- end
46
- end
47
-
48
- ##
49
- # Clock that cannot be set and represents monotonic time since
50
- # some unspecified starting point.
51
- #
52
- # @!visibility private
53
- GLOBAL_MONOTONIC_CLOCK = class_definition.new
54
- private_constant :GLOBAL_MONOTONIC_CLOCK
55
-
56
- class << self
57
- ##
58
- # Returns the current time a tracked by the application monotonic clock.
59
- #
60
- # @return [Float] The current monotonic time when `since` not given else
61
- # the elapsed monotonic time between `since` and the current time
62
- def monotonic_time
63
- GLOBAL_MONOTONIC_CLOCK.get_time
64
- end
65
- end
66
- end