connection_pool 2.1.2 → 2.2.3

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: cba01c34d4f2ac4cd9beb4d71ade9808e66095a0
4
- data.tar.gz: 541e73dc97f93b16a7a2725be6157395a76b2295
2
+ SHA256:
3
+ metadata.gz: e9fefa40cd9db0f5add54482a9f980d808b5bb578e83a5a8bf287c636c41704d
4
+ data.tar.gz: ec25d36c42cfb863e768bf49b18316d955397edaa8862f92d27687ab2a089bf0
5
5
  SHA512:
6
- metadata.gz: d2d9e0deee042e07bd04a4715c97d67bc0c6d59fc6670a307a740024d7ff38ad6fe46dbe9edc482bdef7d6ed51abaeb5297df1a3664c6ec26ba0f2d0e51512c7
7
- data.tar.gz: 2dbf1090f068f706141e92aae4dfefb1f596e96be621e0ad1d4e4824c9abae734f337d7ea2e4c5d7943f612835f4ca429469347f1270359c4e604033329dabc4
6
+ metadata.gz: d8653437078b6334be998d16f4851e12a21e8caca8338a07559c9bfbe646dc6023a7370aad6e33be905b05d6eaab8f249387eaa72b16a478f57c9c497adbbf30
7
+ data.tar.gz: 2a437d085a3f11376338ee32debe5e7ff38fc28fdc8d0d318287c2890bc90ce2d282468981dde43c8391798d113e1391d7998a5f5e571f1021ae99dfff6893e2
@@ -1,14 +1,12 @@
1
1
  ---
2
- sudo: false
3
2
  cache: bundler
4
3
  language: ruby
5
4
  rvm:
6
- - 1.9.3
7
- - 2.0.0
8
- - 2.1.0
9
- - 2.2.0
5
+ - 2.3
6
+ - 2.4
7
+ - 2.5
8
+ - 2.6
9
+ - 2.7
10
10
  - jruby
11
- - rbx-2
12
- notifications:
13
- email:
14
- - drbrain@segment7.net
11
+ jdk:
12
+ - openjdk11
data/Changes.md CHANGED
@@ -1,3 +1,38 @@
1
+ # connection_pool Changelog
2
+
3
+ 2.2.3
4
+ ------
5
+
6
+ - Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130]
7
+ - Use monotonic clock present in all modern Rubies [Tero Tasanen, #109]
8
+ - Remove code hacks necessary for JRuby 1.7
9
+ - Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113]
10
+
11
+ 2.2.2
12
+ ------
13
+
14
+ - Add pool `size` and `available` accessors for metrics and monitoring
15
+ purposes [#97, robholland]
16
+
17
+ 2.2.1
18
+ ------
19
+
20
+ - Allow CP::Wrapper to use an existing pool [#87, etiennebarrie]
21
+ - Use monotonic time for more accurate timeouts [#84, jdantonio]
22
+
23
+ 2.2.0
24
+ ------
25
+
26
+ - Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems
27
+ impossible to safely work around the issue. Please never, ever use
28
+ `Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75]
29
+
30
+ 2.1.3
31
+ ------
32
+
33
+ - Don't increment created count until connection is successfully
34
+ created. [mylesmegyesi, #73]
35
+
1
36
  2.1.2
2
37
  ------
3
38
 
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gemspec(development_group: :runtime)
4
+
5
+ gem "standard"
data/README.md CHANGED
@@ -45,11 +45,13 @@ sections when a resource is not available, or conversely if you are comfortable
45
45
  blocking longer on a particular resource. This is not implemented in the below
46
46
  `ConnectionPool::Wrapper` class.
47
47
 
48
+ ## Migrating to a Connection Pool
49
+
48
50
  You can use `ConnectionPool::Wrapper` to wrap a single global connection,
49
- making it easier to port your connection code over time:
51
+ making it easier to migrate existing connection code over time:
50
52
 
51
53
  ``` ruby
52
- $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect }
54
+ $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.new }
53
55
  $redis.sadd('foo', 1)
54
56
  $redis.smembers('foo')
55
57
  ```
@@ -69,19 +71,36 @@ end
69
71
  Once you've ported your entire system to use `with`, you can simply remove
70
72
  `Wrapper` and use the simpler and faster `ConnectionPool`.
71
73
 
74
+
75
+ ## Shutdown
76
+
72
77
  You can shut down a ConnectionPool instance once it should no longer be used.
73
78
  Further checkout attempts will immediately raise an error but existing checkouts
74
79
  will work.
75
80
 
76
81
  ```ruby
77
82
  cp = ConnectionPool.new { Redis.new }
78
- cp.shutdown { |conn| conn.close }
83
+ cp.shutdown { |conn| conn.quit }
79
84
  ```
80
85
 
81
86
  Shutting down a connection pool will block until all connections are checked in and closed.
82
- Note that shutting down is completely optional; Ruby's garbage collector will reclaim
87
+ **Note that shutting down is completely optional**; Ruby's garbage collector will reclaim
83
88
  unreferenced pools under normal circumstances.
84
89
 
90
+ ## Current State
91
+
92
+ There are several methods that return information about a pool.
93
+
94
+ ```ruby
95
+ cp = ConnectionPool.new(size: 10) { Redis.new }
96
+ cp.size # => 10
97
+ cp.available # => 10
98
+
99
+ cp.with do |conn|
100
+ cp.size # => 10
101
+ cp.available # => 9
102
+ end
103
+ ```
85
104
 
86
105
  Notes
87
106
  -----
@@ -90,14 +109,10 @@ Notes
90
109
  - There is no provision for repairing or checking the health of a connection;
91
110
  connections should be self-repairing. This is true of the Dalli and Redis
92
111
  clients.
93
-
94
-
95
- Install
96
- -------
97
-
98
- ```
99
- $ gem install connection_pool
100
- ```
112
+ - **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see
113
+ occasional silent corruption and mysterious errors. The Timeout API is unsafe
114
+ and cannot be used correctly, ever. Use proper socket timeout options as
115
+ exposed by Net::HTTP, Redis, Dalli, etc.
101
116
 
102
117
 
103
118
  Author
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
- require 'bundler/gem_tasks'
1
+ require "bundler/gem_tasks"
2
2
 
3
- require 'rake/testtask'
3
+ require "rake/testtask"
4
+ require "standard/rake"
4
5
  Rake::TestTask.new
5
6
 
6
- task :default => :test
7
+ task default: :test
@@ -1,21 +1,20 @@
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"
21
20
  end
@@ -1,20 +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
- # Generic connection pool class for e.g. sharing a limited number of network connections
5
- # among many threads. Note: Connections are lazily created.
4
+ class ConnectionPool
5
+ class Error < ::RuntimeError; end
6
+ class PoolShuttingDownError < ::ConnectionPool::Error; end
7
+ class TimeoutError < ::Timeout::Error; end
8
+ end
9
+
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.
6
12
  #
7
13
  # Example usage with block (faster):
8
14
  #
9
15
  # @pool = ConnectionPool.new { Redis.new }
10
- #
11
16
  # @pool.with do |redis|
12
17
  # redis.lpop('my-list') if redis.llen('my-list') > 0
13
18
  # end
14
19
  #
15
20
  # Using optional timeout override (for that single invocation)
16
21
  #
17
- # @pool.with(:timeout => 2.0) do |redis|
22
+ # @pool.with(timeout: 2.0) do |redis|
18
23
  # redis.lpop('my-list') if redis.llen('my-list') > 0
19
24
  # end
20
25
  #
@@ -33,60 +38,57 @@ require_relative 'connection_pool/timed_stack'
33
38
  class ConnectionPool
34
39
  DEFAULTS = {size: 5, timeout: 5}
35
40
 
36
- class Error < RuntimeError
37
- end
38
-
39
41
  def self.wrap(options, &block)
40
42
  Wrapper.new(options, &block)
41
43
  end
42
44
 
43
45
  def initialize(options = {}, &block)
44
- raise ArgumentError, 'Connection pool requires a block' unless block
46
+ raise ArgumentError, "Connection pool requires a block" unless block
45
47
 
46
48
  options = DEFAULTS.merge(options)
47
49
 
48
- @size = options.fetch(:size)
50
+ @size = Integer(options.fetch(:size))
49
51
  @timeout = options.fetch(:timeout)
50
52
 
51
53
  @available = TimedStack.new(@size, &block)
52
- @key = :"current-#{@available.object_id}"
54
+ @key = :"pool-#{@available.object_id}"
55
+ @key_count = :"pool-#{@available.object_id}-count"
53
56
  end
54
57
 
55
58
  def with(options = {})
56
- # Connections can become corrupted via Timeout::Error. Discard
57
- # any connection whose usage after checkout does not finish as expected.
58
- # See #67
59
- success = false
60
- conn = checkout(options)
61
- begin
62
- result = yield conn
63
- success = true # means the connection wasn't interrupted
64
- result
65
- ensure
66
- if success
67
- # everything is roses, we can safely check the connection back in
59
+ Thread.handle_interrupt(Exception => :never) do
60
+ conn = checkout(options)
61
+ begin
62
+ Thread.handle_interrupt(Exception => :immediate) do
63
+ yield conn
64
+ end
65
+ ensure
68
66
  checkin
69
- else
70
- @available.discard!(pop_connection)
71
67
  end
72
68
  end
73
69
  end
74
70
 
75
71
  def checkout(options = {})
76
- conn = if stack.empty?
77
- timeout = options[:timeout] || @timeout
78
- @available.pop(timeout: timeout)
72
+ if ::Thread.current[@key]
73
+ ::Thread.current[@key_count] += 1
74
+ ::Thread.current[@key]
79
75
  else
80
- stack.last
76
+ ::Thread.current[@key_count] = 1
77
+ ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout)
81
78
  end
82
-
83
- stack.push conn
84
- conn
85
79
  end
86
80
 
87
81
  def checkin
88
- conn = pop_connection # mutates stack, must be on its own line
89
- @available.push(conn) if stack.empty?
82
+ if ::Thread.current[@key]
83
+ if ::Thread.current[@key_count] == 1
84
+ @available.push(::Thread.current[@key])
85
+ ::Thread.current[@key] = nil
86
+ else
87
+ ::Thread.current[@key_count] -= 1
88
+ end
89
+ else
90
+ raise ConnectionPool::Error, "no connections are checked out"
91
+ end
90
92
 
91
93
  nil
92
94
  end
@@ -95,43 +97,14 @@ class ConnectionPool
95
97
  @available.shutdown(&block)
96
98
  end
97
99
 
98
- private
99
-
100
- def pop_connection
101
- if stack.empty?
102
- raise ConnectionPool::Error, 'no connections are checked out'
103
- else
104
- stack.pop
105
- end
106
- end
107
-
108
- def stack
109
- ::Thread.current[@key] ||= []
110
- end
111
-
112
- class Wrapper < ::BasicObject
113
- METHODS = [:with, :pool_shutdown]
114
-
115
- def initialize(options = {}, &block)
116
- @pool = ::ConnectionPool.new(options, &block)
117
- end
118
-
119
- def with(&block)
120
- @pool.with(&block)
121
- end
100
+ # Size of this connection pool
101
+ attr_reader :size
122
102
 
123
- def pool_shutdown(&block)
124
- @pool.shutdown(&block)
125
- end
126
-
127
- def respond_to?(id, *args)
128
- METHODS.include?(id) || with { |c| c.respond_to?(id, *args) }
129
- end
130
-
131
- def method_missing(name, *args, &block)
132
- with do |connection|
133
- connection.send(name, *args, &block)
134
- end
135
- end
103
+ # Number of pool entries available for checkout at this instant.
104
+ def available
105
+ @available.length
136
106
  end
137
107
  end
108
+
109
+ require "connection_pool/timed_stack"
110
+ 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
@@ -27,6 +18,7 @@ class ConnectionPool::PoolShuttingDownError < RuntimeError; end
27
18
  # #=> raises Timeout::Error 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,7 +49,7 @@ 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
@@ -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,8 +73,8 @@ 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
@@ -117,30 +109,12 @@ class ConnectionPool::TimedStack
117
109
  @max - @created + @que.length
118
110
  end
119
111
 
120
- ##
121
- # Indicates that a connection isn't coming back, allowing a new one to be
122
- # created to replace it.
123
-
124
- def discard!(obj)
125
- @mutex.synchronize do
126
- if @shutdown_block
127
- @shutdown_block.call(obj)
128
- else
129
- # try to shut down the connection before throwing it away
130
- if obj.respond_to?(:close) # Dalli::Client
131
- obj.close rescue nil
132
- elsif obj.respond_to?(:disconnect!) # Redis
133
- obj.disconnect! rescue nil
134
- end
135
- @created -= 1
136
- end
112
+ private
137
113
 
138
- @resource.broadcast
139
- end
114
+ def current_time
115
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
140
116
  end
141
117
 
142
- private
143
-
144
118
  ##
145
119
  # This is an extension point for TimedStack and is called with a mutex.
146
120
  #
@@ -188,8 +162,9 @@ class ConnectionPool::TimedStack
188
162
 
189
163
  def try_create(options = nil)
190
164
  unless @created == @max
165
+ object = @create_block.call
191
166
  @created += 1
192
- @create_block.call
167
+ object
193
168
  end
194
169
  end
195
170
  end
@@ -1,3 +1,3 @@
1
1
  class ConnectionPool
2
- VERSION = "2.1.2"
2
+ VERSION = "2.2.3"
3
3
  end
@@ -0,0 +1,43 @@
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
+ def method_missing(name, *args, &block)
36
+ with do |connection|
37
+ connection.send(name, *args, &block)
38
+ end
39
+ end
40
+ # rubocop:enable Style/MethodMissingSuper
41
+ # rubocop:enable Style/MissingRespondToMissing
42
+ end
43
+ end
@@ -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,15 +1,16 @@
1
- require_relative 'helper'
1
+ require_relative "helper"
2
2
 
3
3
  class TestConnectionPool < Minitest::Test
4
-
5
4
  class NetworkConnection
5
+ SLEEP_TIME = 0.1
6
+
6
7
  def initialize
7
8
  @x = 0
8
9
  end
9
10
 
10
11
  def do_something
11
12
  @x += 1
12
- sleep 0.05
13
+ sleep SLEEP_TIME
13
14
  @x
14
15
  end
15
16
 
@@ -19,7 +20,7 @@ class TestConnectionPool < Minitest::Test
19
20
 
20
21
  def do_something_with_block
21
22
  @x += yield
22
- sleep 0.05
23
+ sleep SLEEP_TIME
23
24
  @x
24
25
  end
25
26
 
@@ -41,12 +42,12 @@ class TestConnectionPool < Minitest::Test
41
42
  end
42
43
 
43
44
  def use_pool(pool, size)
44
- Array.new(size) do
45
+ Array.new(size) {
45
46
  Thread.new do
46
- pool.with do sleep end
47
+ pool.with { sleep }
47
48
  end
48
- end.each do |thread|
49
- Thread.pass until thread.status == 'sleep'
49
+ }.each do |thread|
50
+ Thread.pass until thread.status == "sleep"
50
51
  end
51
52
  end
52
53
 
@@ -58,33 +59,38 @@ class TestConnectionPool < Minitest::Test
58
59
  end
59
60
 
60
61
  def test_basic_multithreaded_usage
61
- pool = ConnectionPool.new(:size => 5) { NetworkConnection.new }
62
+ pool_size = 5
63
+ pool = ConnectionPool.new(size: pool_size) { NetworkConnection.new }
64
+
65
+ start = Time.new
62
66
 
63
- threads = Array.new(15) do
67
+ generations = 3
68
+
69
+ result = Array.new(pool_size * generations) {
64
70
  Thread.new do
65
71
  pool.with do |net|
66
72
  net.do_something
67
73
  end
68
74
  end
69
- end
75
+ }.map(&:value)
70
76
 
71
- a = Time.now
72
- result = threads.map(&:value)
73
- b = Time.now
74
- assert_operator((b - a), :>, 0.125)
75
- assert_equal([1,2,3].cycle(5).sort, result.sort)
77
+ finish = Time.new
78
+
79
+ assert_equal((1..generations).cycle(pool_size).sort, result.sort)
80
+
81
+ assert_operator(finish - start, :>, generations * NetworkConnection::SLEEP_TIME)
76
82
  end
77
83
 
78
84
  def test_timeout
79
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { NetworkConnection.new }
80
- thread = Thread.new do
85
+ pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
86
+ thread = Thread.new {
81
87
  pool.with do |net|
82
88
  net.do_something
83
89
  sleep 0.01
84
90
  end
85
- end
91
+ }
86
92
 
87
- Thread.pass while thread.status == 'run'
93
+ Thread.pass while thread.status == "run"
88
94
 
89
95
  assert_raises Timeout::Error do
90
96
  pool.with { |net| net.do_something }
@@ -98,79 +104,126 @@ class TestConnectionPool < Minitest::Test
98
104
  end
99
105
 
100
106
  def test_with
101
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
107
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
102
108
 
103
109
  pool.with do
104
- assert_raises Timeout::Error do
105
- Thread.new { pool.checkout }.join
106
- end
110
+ Thread.new {
111
+ assert_raises Timeout::Error do
112
+ pool.checkout
113
+ end
114
+ }.join
107
115
  end
108
116
 
109
117
  assert Thread.new { pool.checkout }.join
110
118
  end
111
119
 
112
- def test_with_with_dangerous_timeouts
113
- case RUBY_ENGINE.to_sym
114
- when :jruby
115
- skip('JRuby GC dislikes this test')
116
- when :ruby
117
- if RUBY_VERSION == '2.0.0' && RUBY_PATCHLEVEL == 598
118
- skip("#{RUBY_VERSION}p#{RUBY_PATCHLEVEL} GC dislikes this test")
120
+ def test_with_timeout
121
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
122
+
123
+ assert_raises Timeout::Error do
124
+ Timeout.timeout(0.01) do
125
+ pool.with do |obj|
126
+ assert_equal 0, pool.available
127
+ sleep 0.015
128
+ end
119
129
  end
120
130
  end
131
+ assert_equal 1, pool.available
132
+ end
121
133
 
122
- marker_class = Class.new
123
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { marker_class.new }
134
+ def test_invalid_size
135
+ assert_raises ArgumentError, TypeError do
136
+ ConnectionPool.new(timeout: 0, size: nil) { Object.new }
137
+ end
138
+ assert_raises ArgumentError, TypeError do
139
+ ConnectionPool.new(timeout: 0, size: "") { Object.new }
140
+ end
141
+ end
124
142
 
125
- # no "connections" allocated yet
126
- assert_equal [], ObjectSpace.each_object(marker_class).to_a
143
+ def test_handle_interrupt_ensures_checkin
144
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
145
+ def pool.checkout(options)
146
+ sleep 0.015
147
+ super
148
+ end
127
149
 
128
- checkin_time = 0.05
150
+ did_something = false
129
151
 
130
- assert_raises Timeout::Error do
131
- Timeout.timeout(checkin_time) do
132
- pool.with do
133
- # a "connection" has been allocated
134
- refute_equal [], ObjectSpace.each_object(marker_class).to_a
135
- sleep 2 * checkin_time
152
+ action = lambda do
153
+ Timeout.timeout(0.01) do
154
+ pool.with do |obj|
155
+ did_something = true
156
+ # Timeout::Error will be triggered by any non-trivial Ruby code
157
+ # executed here since it couldn't be raised during checkout.
158
+ # It looks like setting the local variable above does not trigger
159
+ # the Timeout check in MRI 2.2.1.
160
+ obj.tap { obj.hash }
136
161
  end
137
162
  end
138
163
  end
139
164
 
140
- GC.start
165
+ if RUBY_ENGINE == "ruby"
166
+ # These asserts rely on the Ruby implementation reaching `did_something =
167
+ # true` before the interrupt is detected by the thread. Interrupt
168
+ # detection timing is implementation-specific in practice, with JRuby,
169
+ # Rubinius, and TruffleRuby all having different interrupt timings to MRI.
170
+ # In fact they generally detect interrupts more quickly than MRI, so they
171
+ # may not reach `did_something = true` before detecting the interrupt.
172
+
173
+ assert_raises Timeout::Error, &action
174
+
175
+ assert did_something
176
+ else
177
+ action.call
178
+ end
179
+
180
+ assert_equal 1, pool.available
181
+ end
182
+
183
+ def test_explicit_return
184
+ pool = ConnectionPool.new(timeout: 0, size: 1) {
185
+ mock = Minitest::Mock.new
186
+ def mock.disconnect!
187
+ raise "should not disconnect upon explicit return"
188
+ end
189
+ mock
190
+ }
141
191
 
142
- # no dangling references to this "connection" remain
143
- assert_equal [], ObjectSpace.each_object(marker_class).to_a
192
+ pool.with do |conn|
193
+ return true
194
+ end
144
195
  end
145
196
 
146
197
  def test_with_timeout_override
147
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { NetworkConnection.new }
198
+ pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
148
199
 
149
- t = Thread.new do
200
+ t = Thread.new {
150
201
  pool.with do |net|
151
202
  net.do_something
152
203
  sleep 0.01
153
204
  end
154
- end
205
+ }
155
206
 
156
- Thread.pass while t.status == 'run'
207
+ Thread.pass while t.status == "run"
157
208
 
158
209
  assert_raises Timeout::Error do
159
210
  pool.with { |net| net.do_something }
160
211
  end
161
212
 
162
- pool.with(:timeout => 0.1) do |conn|
213
+ pool.with(timeout: 2 * NetworkConnection::SLEEP_TIME) do |conn|
163
214
  refute_nil conn
164
215
  end
165
216
  end
166
217
 
167
218
  def test_checkin
168
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { NetworkConnection.new }
219
+ pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
169
220
  conn = pool.checkout
170
221
 
171
- assert_raises Timeout::Error do
172
- Thread.new { pool.checkout }.join
173
- end
222
+ Thread.new {
223
+ assert_raises Timeout::Error do
224
+ pool.checkout
225
+ end
226
+ }.join
174
227
 
175
228
  pool.checkin
176
229
 
@@ -178,22 +231,19 @@ class TestConnectionPool < Minitest::Test
178
231
  end
179
232
 
180
233
  def test_returns_value
181
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
182
- assert_equal 1, pool.with {|o| 1 }
234
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
235
+ assert_equal 1, pool.with { |o| 1 }
183
236
  end
184
237
 
185
238
  def test_checkin_never_checkout
186
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
187
-
188
- e = assert_raises ConnectionPool::Error do
189
- pool.checkin
190
- end
239
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
191
240
 
192
- assert_equal 'no connections are checked out', e.message
241
+ e = assert_raises(ConnectionPool::Error) { pool.checkin }
242
+ assert_equal "no connections are checked out", e.message
193
243
  end
194
244
 
195
245
  def test_checkin_no_current_checkout
196
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
246
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
197
247
 
198
248
  pool.checkout
199
249
  pool.checkin
@@ -204,18 +254,18 @@ class TestConnectionPool < Minitest::Test
204
254
  end
205
255
 
206
256
  def test_checkin_twice
207
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { Object.new }
257
+ pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new }
208
258
 
209
259
  pool.checkout
210
260
  pool.checkout
211
261
 
212
262
  pool.checkin
213
263
 
214
- assert_raises Timeout::Error do
215
- Thread.new do
264
+ Thread.new {
265
+ assert_raises Timeout::Error do
216
266
  pool.checkout
217
- end.join
218
- end
267
+ end
268
+ }.join
219
269
 
220
270
  pool.checkin
221
271
 
@@ -223,7 +273,7 @@ class TestConnectionPool < Minitest::Test
223
273
  end
224
274
 
225
275
  def test_checkout
226
- pool = ConnectionPool.new(:size => 1) { NetworkConnection.new }
276
+ pool = ConnectionPool.new(size: 1) { NetworkConnection.new }
227
277
 
228
278
  conn = pool.checkout
229
279
 
@@ -233,18 +283,18 @@ class TestConnectionPool < Minitest::Test
233
283
  end
234
284
 
235
285
  def test_checkout_multithread
236
- pool = ConnectionPool.new(:size => 2) { NetworkConnection.new }
286
+ pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
237
287
  conn = pool.checkout
238
288
 
239
- t = Thread.new do
289
+ t = Thread.new {
240
290
  pool.checkout
241
- end
291
+ }
242
292
 
243
293
  refute_same conn, t.value
244
294
  end
245
295
 
246
296
  def test_checkout_timeout
247
- pool = ConnectionPool.new(:timeout => 0, :size => 0) { Object.new }
297
+ pool = ConnectionPool.new(timeout: 0, size: 0) { Object.new }
248
298
 
249
299
  assert_raises Timeout::Error do
250
300
  pool.checkout
@@ -252,26 +302,26 @@ class TestConnectionPool < Minitest::Test
252
302
  end
253
303
 
254
304
  def test_checkout_timeout_override
255
- pool = ConnectionPool.new(:timeout => 0, :size => 1) { NetworkConnection.new }
305
+ pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new }
256
306
 
257
- thread = Thread.new do
307
+ thread = Thread.new {
258
308
  pool.with do |net|
259
309
  net.do_something
260
310
  sleep 0.01
261
311
  end
262
- end
312
+ }
263
313
 
264
- Thread.pass while thread.status == 'run'
314
+ Thread.pass while thread.status == "run"
265
315
 
266
316
  assert_raises Timeout::Error do
267
317
  pool.checkout
268
318
  end
269
319
 
270
- assert pool.checkout :timeout => 0.1
320
+ assert pool.checkout timeout: 2 * NetworkConnection::SLEEP_TIME
271
321
  end
272
322
 
273
323
  def test_passthru
274
- pool = ConnectionPool.wrap(:timeout => 0.1, :size => 1) { NetworkConnection.new }
324
+ pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
275
325
  assert_equal 1, pool.do_something
276
326
  assert_equal 2, pool.do_something
277
327
  assert_equal 5, pool.do_something_with_block { 3 }
@@ -279,7 +329,7 @@ class TestConnectionPool < Minitest::Test
279
329
  end
280
330
 
281
331
  def test_passthru_respond_to
282
- pool = ConnectionPool.wrap(:timeout => 0.1, :size => 1) { NetworkConnection.new }
332
+ pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
283
333
  assert pool.respond_to?(:with)
284
334
  assert pool.respond_to?(:do_something)
285
335
  assert pool.respond_to?(:do_magic)
@@ -287,67 +337,67 @@ class TestConnectionPool < Minitest::Test
287
337
  end
288
338
 
289
339
  def test_return_value
290
- pool = ConnectionPool.new(:timeout => 0.1, :size => 1) { NetworkConnection.new }
291
- result = pool.with do |net|
340
+ pool = ConnectionPool.new(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new }
341
+ result = pool.with { |net|
292
342
  net.fast
293
- end
343
+ }
294
344
  assert_equal 1, result
295
345
  end
296
346
 
297
347
  def test_heavy_threading
298
- pool = ConnectionPool.new(:timeout => 0.5, :size => 3) { NetworkConnection.new }
348
+ pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new }
299
349
 
300
- threads = Array.new(20) do
350
+ threads = Array.new(20) {
301
351
  Thread.new do
302
352
  pool.with do |net|
303
353
  sleep 0.01
304
354
  end
305
355
  end
306
- end
356
+ }
307
357
 
308
358
  threads.map { |thread| thread.join }
309
359
  end
310
360
 
311
361
  def test_reuses_objects_when_pool_not_saturated
312
- pool = ConnectionPool.new(:size => 5) { NetworkConnection.new }
362
+ pool = ConnectionPool.new(size: 5) { NetworkConnection.new }
313
363
 
314
- ids = 10.times.map do
364
+ ids = 10.times.map {
315
365
  pool.with { |c| c.object_id }
316
- end
366
+ }
317
367
 
318
368
  assert_equal 1, ids.uniq.size
319
369
  end
320
370
 
321
371
  def test_nested_checkout
322
372
  recorder = Recorder.new
323
- pool = ConnectionPool.new(:size => 1) { recorder }
373
+ pool = ConnectionPool.new(size: 1) { recorder }
324
374
  pool.with do |r_outer|
325
- @other = Thread.new do |t|
375
+ @other = Thread.new { |t|
326
376
  pool.with do |r_other|
327
- r_other.do_work('other')
377
+ r_other.do_work("other")
328
378
  end
329
- end
379
+ }
330
380
 
331
381
  pool.with do |r_inner|
332
- r_inner.do_work('inner')
382
+ r_inner.do_work("inner")
333
383
  end
334
384
 
335
385
  Thread.pass
336
386
 
337
- r_outer.do_work('outer')
387
+ r_outer.do_work("outer")
338
388
  end
339
389
 
340
390
  @other.join
341
391
 
342
- assert_equal ['inner', 'outer', 'other'], recorder.calls
392
+ assert_equal ["inner", "outer", "other"], recorder.calls
343
393
  end
344
394
 
345
395
  def test_shutdown_is_executed_for_all_connections
346
396
  recorders = []
347
397
 
348
- pool = ConnectionPool.new(:size => 3) do
398
+ pool = ConnectionPool.new(size: 3) {
349
399
  Recorder.new.tap { |r| recorders << r }
350
- end
400
+ }
351
401
 
352
402
  threads = use_pool pool, 3
353
403
 
@@ -361,9 +411,9 @@ class TestConnectionPool < Minitest::Test
361
411
  end
362
412
 
363
413
  def test_raises_error_after_shutting_down
364
- pool = ConnectionPool.new(:size => 1) { true }
414
+ pool = ConnectionPool.new(size: 1) { true }
365
415
 
366
- pool.shutdown { }
416
+ pool.shutdown {}
367
417
 
368
418
  assert_raises ConnectionPool::PoolShuttingDownError do
369
419
  pool.checkout
@@ -373,9 +423,9 @@ class TestConnectionPool < Minitest::Test
373
423
  def test_runs_shutdown_block_asynchronously_if_connection_was_in_use
374
424
  recorders = []
375
425
 
376
- pool = ConnectionPool.new(:size => 3) do
426
+ pool = ConnectionPool.new(size: 3) {
377
427
  Recorder.new.tap { |r| recorders << r }
378
- end
428
+ }
379
429
 
380
430
  threads = use_pool pool, 2
381
431
 
@@ -395,7 +445,7 @@ class TestConnectionPool < Minitest::Test
395
445
  end
396
446
 
397
447
  def test_raises_an_error_if_shutdown_is_called_without_a_block
398
- pool = ConnectionPool.new(:size => 1) { }
448
+ pool = ConnectionPool.new(size: 1) {}
399
449
 
400
450
  assert_raises ArgumentError do
401
451
  pool.shutdown
@@ -405,9 +455,9 @@ class TestConnectionPool < Minitest::Test
405
455
  def test_shutdown_is_executed_for_all_connections_in_wrapped_pool
406
456
  recorders = []
407
457
 
408
- wrapper = ConnectionPool::Wrapper.new(:size => 3) do
458
+ wrapper = ConnectionPool::Wrapper.new(size: 3) {
409
459
  Recorder.new.tap { |r| recorders << r }
410
- end
460
+ }
411
461
 
412
462
  threads = use_pool wrapper, 3
413
463
 
@@ -420,6 +470,11 @@ class TestConnectionPool < Minitest::Test
420
470
  assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls }
421
471
  end
422
472
 
473
+ def test_wrapper_wrapped_pool
474
+ wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
475
+ assert_equal ConnectionPool, wrapper.wrapped_pool.class
476
+ end
477
+
423
478
  def test_wrapper_method_missing
424
479
  wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new }
425
480
 
@@ -436,17 +491,17 @@ class TestConnectionPool < Minitest::Test
436
491
  end
437
492
 
438
493
  def test_wrapper_with
439
- wrapper = ConnectionPool::Wrapper.new(:timeout => 0, :size => 1) { Object.new }
494
+ wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { Object.new }
440
495
 
441
496
  wrapper.with do
442
- assert_raises Timeout::Error do
443
- Thread.new do
444
- wrapper.with { flunk 'connection checked out :(' }
445
- end.join
446
- end
497
+ Thread.new {
498
+ assert_raises Timeout::Error do
499
+ wrapper.with { flunk "connection checked out :(" }
500
+ end
501
+ }.join
447
502
  end
448
503
 
449
- assert Thread.new { wrapper.with { } }.join
504
+ assert Thread.new { wrapper.with {} }.join
450
505
  end
451
506
 
452
507
  class ConnWithEval
@@ -461,4 +516,38 @@ class TestConnectionPool < Minitest::Test
461
516
  assert_equal "eval'ed 1", wrapper.eval(1)
462
517
  end
463
518
 
519
+ def test_wrapper_with_connection_pool
520
+ recorder = Recorder.new
521
+ pool = ConnectionPool.new(size: 1) { recorder }
522
+ wrapper = ConnectionPool::Wrapper.new(pool: pool)
523
+
524
+ pool.with { |r| r.do_work("with") }
525
+ wrapper.do_work("wrapped")
526
+
527
+ assert_equal ["with", "wrapped"], recorder.calls
528
+ end
529
+
530
+ def test_stats_without_active_connection
531
+ pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
532
+
533
+ assert_equal(2, pool.size)
534
+ assert_equal(2, pool.available)
535
+ end
536
+
537
+ def test_stats_with_active_connection
538
+ pool = ConnectionPool.new(size: 2) { NetworkConnection.new }
539
+
540
+ pool.with do
541
+ assert_equal(1, pool.available)
542
+ end
543
+ end
544
+
545
+ def test_stats_with_string_size
546
+ pool = ConnectionPool.new(size: "2") { NetworkConnection.new }
547
+
548
+ pool.with do
549
+ assert_equal(2, pool.size)
550
+ assert_equal(1, pool.available)
551
+ end
552
+ end
464
553
  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
@@ -34,6 +33,25 @@ class TestConnectionPoolTimedStack < Minitest::Test
34
33
  assert_equal 1, stack.length
35
34
  end
36
35
 
36
+ def test_object_creation_fails
37
+ stack = ConnectionPool::TimedStack.new(2) { raise "failure" }
38
+
39
+ begin
40
+ stack.pop
41
+ rescue => error
42
+ assert_equal "failure", error.message
43
+ end
44
+
45
+ begin
46
+ stack.pop
47
+ rescue => error
48
+ assert_equal "failure", error.message
49
+ end
50
+
51
+ refute_empty stack
52
+ assert_equal 2, stack.length
53
+ end
54
+
37
55
  def test_pop
38
56
  object = Object.new
39
57
  @stack.push object
@@ -44,19 +62,13 @@ class TestConnectionPoolTimedStack < Minitest::Test
44
62
  end
45
63
 
46
64
  def test_pop_empty
47
- e = assert_raises Timeout::Error do
48
- @stack.pop timeout: 0
49
- end
50
-
51
- 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
52
67
  end
53
68
 
54
69
  def test_pop_empty_2_0_compatibility
55
- e = assert_raises Timeout::Error do
56
- @stack.pop 0
57
- end
58
-
59
- assert_equal 'Waited 0 sec', e.message
70
+ e = assert_raises(Timeout::Error) { @stack.pop 0 }
71
+ assert_equal "Waited 0 sec", e.message
60
72
  end
61
73
 
62
74
  def test_pop_full
@@ -69,11 +81,11 @@ class TestConnectionPoolTimedStack < Minitest::Test
69
81
  end
70
82
 
71
83
  def test_pop_wait
72
- thread = Thread.start do
84
+ thread = Thread.start {
73
85
  @stack.pop
74
- end
86
+ }
75
87
 
76
- Thread.pass while thread.status == 'run'
88
+ Thread.pass while thread.status == "run"
77
89
 
78
90
  object = Object.new
79
91
 
@@ -83,7 +95,7 @@ class TestConnectionPoolTimedStack < Minitest::Test
83
95
  end
84
96
 
85
97
  def test_pop_shutdown
86
- @stack.shutdown { }
98
+ @stack.shutdown {}
87
99
 
88
100
  assert_raises ConnectionPool::PoolShuttingDownError do
89
101
  @stack.pop
@@ -125,6 +137,4 @@ class TestConnectionPoolTimedStack < Minitest::Test
125
137
  refute_empty called
126
138
  assert_empty @stack
127
139
  end
128
-
129
140
  end
130
-
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: connection_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 2.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Perham
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-02 00:00:00.000000000 Z
12
+ date: 2020-06-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -72,6 +72,7 @@ files:
72
72
  - lib/connection_pool.rb
73
73
  - lib/connection_pool/timed_stack.rb
74
74
  - lib/connection_pool/version.rb
75
+ - lib/connection_pool/wrapper.rb
75
76
  - test/helper.rb
76
77
  - test/test_connection_pool.rb
77
78
  - test/test_connection_pool_timed_stack.rb
@@ -94,8 +95,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
95
  - !ruby/object:Gem::Version
95
96
  version: '0'
96
97
  requirements: []
97
- rubyforge_project:
98
- rubygems_version: 2.4.5
98
+ rubygems_version: 3.1.2
99
99
  signing_key:
100
100
  specification_version: 4
101
101
  summary: Generic connection pool for Ruby