connection_pool-2.2.2/0000755000004100000410000000000013312077323014736 5ustar www-datawww-dataconnection_pool-2.2.2/.travis.yml0000644000004100000410000000014513312077323017047 0ustar www-datawww-data--- sudo: false cache: bundler language: ruby rvm: - 2.2.9 - 2.3.6 - 2.4.3 - 2.5.0 - jruby connection_pool-2.2.2/test/0000755000004100000410000000000013312077323015715 5ustar www-datawww-dataconnection_pool-2.2.2/test/test_connection_pool_timed_stack.rb0000644000004100000410000000460113312077323025041 0ustar www-datawww-datarequire_relative 'helper' class TestConnectionPoolTimedStack < Minitest::Test def setup @stack = ConnectionPool::TimedStack.new { Object.new } end def test_empty_eh stack = ConnectionPool::TimedStack.new(1) { Object.new } refute_empty stack popped = stack.pop assert_empty stack stack.push popped refute_empty stack end def test_length stack = ConnectionPool::TimedStack.new(1) { Object.new } assert_equal 1, stack.length popped = stack.pop assert_equal 0, stack.length stack.push popped assert_equal 1, stack.length end def test_object_creation_fails stack = ConnectionPool::TimedStack.new(2) { raise 'failure' } begin stack.pop rescue => error assert_equal 'failure', error.message end begin stack.pop rescue => error assert_equal 'failure', error.message end refute_empty stack assert_equal 2, stack.length end def test_pop object = Object.new @stack.push object popped = @stack.pop assert_same object, popped end def test_pop_empty e = assert_raises Timeout::Error do @stack.pop timeout: 0 end assert_equal 'Waited 0 sec', e.message end def test_pop_empty_2_0_compatibility e = assert_raises Timeout::Error do @stack.pop 0 end assert_equal 'Waited 0 sec', e.message end def test_pop_full stack = ConnectionPool::TimedStack.new(1) { Object.new } popped = stack.pop refute_nil popped assert_empty stack end def test_pop_wait thread = Thread.start do @stack.pop end Thread.pass while thread.status == 'run' object = Object.new @stack.push object assert_same object, thread.value end def test_pop_shutdown @stack.shutdown { } assert_raises ConnectionPool::PoolShuttingDownError do @stack.pop end end def test_push stack = ConnectionPool::TimedStack.new(1) { Object.new } conn = stack.pop stack.push conn refute_empty stack end def test_push_shutdown called = [] @stack.shutdown do |object| called << object end @stack.push Object.new refute_empty called assert_empty @stack end def test_shutdown @stack.push Object.new called = [] @stack.shutdown do |object| called << object end refute_empty called assert_empty @stack end end connection_pool-2.2.2/test/helper.rb0000644000004100000410000000017513312077323017524 0ustar www-datawww-datagem 'minitest' require 'minitest/pride' require 'minitest/autorun' $VERBOSE = 1 require_relative '../lib/connection_pool' connection_pool-2.2.2/test/test_connection_pool.rb0000644000004100000410000002646313312077323022504 0ustar www-datawww-datarequire_relative 'helper' class TestConnectionPool < Minitest::Test class NetworkConnection SLEEP_TIME = 0.1 def initialize @x = 0 end def do_something @x += 1 sleep SLEEP_TIME @x end def fast @x += 1 end def do_something_with_block @x += yield sleep SLEEP_TIME @x end def respond_to?(method_id, *args) method_id == :do_magic || super(method_id, *args) end end class Recorder def initialize @calls = [] end attr_reader :calls def do_work(label) @calls << label end end def use_pool(pool, size) Array.new(size) do Thread.new do pool.with do sleep end end end.each do |thread| Thread.pass until thread.status == 'sleep' end end def kill_threads(threads) threads.each do |thread| thread.kill thread.join end end def test_basic_multithreaded_usage pool_size = 5 pool = ConnectionPool.new(size: pool_size) { NetworkConnection.new } start = Time.new generations = 3 result = Array.new(pool_size * generations) do Thread.new do pool.with do |net| net.do_something end end end.map(&:value) finish = Time.new assert_equal((1..generations).cycle(pool_size).sort, result.sort) assert_operator(finish - start, :>, generations * NetworkConnection::SLEEP_TIME) end def test_timeout pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new } thread = Thread.new do pool.with do |net| net.do_something sleep 0.01 end end Thread.pass while thread.status == 'run' assert_raises Timeout::Error do pool.with { |net| net.do_something } end thread.join pool.with do |conn| refute_nil conn end end def test_with pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } pool.with do assert_raises Timeout::Error do Thread.new { pool.checkout }.join end end assert Thread.new { pool.checkout }.join end def test_with_timeout pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } assert_raises Timeout::Error do Timeout.timeout(0.01) do pool.with do |obj| assert_equal 0, pool.instance_variable_get(:@available).instance_variable_get(:@que).size sleep 0.015 end end end assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size end def test_checkout_ignores_timeout skip("Thread.handle_interrupt not available") unless Thread.respond_to?(:handle_interrupt) pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } def pool.checkout(options) sleep 0.015 super end did_something = false assert_raises Timeout::Error do Timeout.timeout(0.01) do pool.with do |obj| did_something = true # Timeout::Error will be triggered by any non-trivial Ruby code # executed here since it couldn't be raised during checkout. # It looks like setting the local variable above does not trigger # the Timeout check in MRI 2.2.1. obj.tap { obj.hash } end end end assert did_something assert_equal 1, pool.instance_variable_get(:@available).instance_variable_get(:@que).size end def test_explicit_return pool = ConnectionPool.new(timeout: 0, size: 1) do mock = Minitest::Mock.new def mock.disconnect! raise "should not disconnect upon explicit return" end mock end pool.with do |conn| return true end end def test_with_timeout_override pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new } t = Thread.new do pool.with do |net| net.do_something sleep 0.01 end end Thread.pass while t.status == 'run' assert_raises Timeout::Error do pool.with { |net| net.do_something } end pool.with(timeout: 2 * NetworkConnection::SLEEP_TIME) do |conn| refute_nil conn end end def test_checkin pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new } conn = pool.checkout assert_raises Timeout::Error do Thread.new { pool.checkout }.join end pool.checkin assert_same conn, Thread.new { pool.checkout }.value end def test_returns_value pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } assert_equal 1, pool.with {|o| 1 } end def test_checkin_never_checkout pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } e = assert_raises ConnectionPool::Error do pool.checkin end assert_equal 'no connections are checked out', e.message end def test_checkin_no_current_checkout pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } pool.checkout pool.checkin assert_raises ConnectionPool::Error do pool.checkin end end def test_checkin_twice pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } pool.checkout pool.checkout pool.checkin assert_raises Timeout::Error do Thread.new do pool.checkout end.join end pool.checkin assert Thread.new { pool.checkout }.join end def test_checkout pool = ConnectionPool.new(size: 1) { NetworkConnection.new } conn = pool.checkout assert_kind_of NetworkConnection, conn assert_same conn, pool.checkout end def test_checkout_multithread pool = ConnectionPool.new(size: 2) { NetworkConnection.new } conn = pool.checkout t = Thread.new do pool.checkout end refute_same conn, t.value end def test_checkout_timeout pool = ConnectionPool.new(timeout: 0, size: 0) { Object.new } assert_raises Timeout::Error do pool.checkout end end def test_checkout_timeout_override pool = ConnectionPool.new(timeout: 0, size: 1) { NetworkConnection.new } thread = Thread.new do pool.with do |net| net.do_something sleep 0.01 end end Thread.pass while thread.status == 'run' assert_raises Timeout::Error do pool.checkout end assert pool.checkout timeout: 2 * NetworkConnection::SLEEP_TIME end def test_passthru pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new } assert_equal 1, pool.do_something assert_equal 2, pool.do_something assert_equal 5, pool.do_something_with_block { 3 } assert_equal 6, pool.with { |net| net.fast } end def test_passthru_respond_to pool = ConnectionPool.wrap(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new } assert pool.respond_to?(:with) assert pool.respond_to?(:do_something) assert pool.respond_to?(:do_magic) refute pool.respond_to?(:do_lots_of_magic) end def test_return_value pool = ConnectionPool.new(timeout: 2 * NetworkConnection::SLEEP_TIME, size: 1) { NetworkConnection.new } result = pool.with do |net| net.fast end assert_equal 1, result end def test_heavy_threading pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new } threads = Array.new(20) do Thread.new do pool.with do |net| sleep 0.01 end end end threads.map { |thread| thread.join } end def test_reuses_objects_when_pool_not_saturated pool = ConnectionPool.new(size: 5) { NetworkConnection.new } ids = 10.times.map do pool.with { |c| c.object_id } end assert_equal 1, ids.uniq.size end def test_nested_checkout recorder = Recorder.new pool = ConnectionPool.new(size: 1) { recorder } pool.with do |r_outer| @other = Thread.new do |t| pool.with do |r_other| r_other.do_work('other') end end pool.with do |r_inner| r_inner.do_work('inner') end Thread.pass r_outer.do_work('outer') end @other.join assert_equal ['inner', 'outer', 'other'], recorder.calls end def test_shutdown_is_executed_for_all_connections recorders = [] pool = ConnectionPool.new(size: 3) do Recorder.new.tap { |r| recorders << r } end threads = use_pool pool, 3 pool.shutdown do |recorder| recorder.do_work("shutdown") end kill_threads(threads) assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls } end def test_raises_error_after_shutting_down pool = ConnectionPool.new(size: 1) { true } pool.shutdown { } assert_raises ConnectionPool::PoolShuttingDownError do pool.checkout end end def test_runs_shutdown_block_asynchronously_if_connection_was_in_use recorders = [] pool = ConnectionPool.new(size: 3) do Recorder.new.tap { |r| recorders << r } end threads = use_pool pool, 2 pool.checkout pool.shutdown do |recorder| recorder.do_work("shutdown") end kill_threads(threads) assert_equal [["shutdown"], ["shutdown"], []], recorders.map { |r| r.calls } pool.checkin assert_equal [["shutdown"], ["shutdown"], ["shutdown"]], recorders.map { |r| r.calls } end def test_raises_an_error_if_shutdown_is_called_without_a_block pool = ConnectionPool.new(size: 1) { } assert_raises ArgumentError do pool.shutdown end end def test_shutdown_is_executed_for_all_connections_in_wrapped_pool recorders = [] wrapper = ConnectionPool::Wrapper.new(size: 3) do Recorder.new.tap { |r| recorders << r } end threads = use_pool wrapper, 3 wrapper.pool_shutdown do |recorder| recorder.do_work("shutdown") end kill_threads(threads) assert_equal [["shutdown"]] * 3, recorders.map { |r| r.calls } end def test_wrapper_method_missing wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new } assert_equal 1, wrapper.fast end def test_wrapper_respond_to_eh wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new } assert_respond_to wrapper, :with assert_respond_to wrapper, :fast refute_respond_to wrapper, :"nonexistent method" end def test_wrapper_with wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { Object.new } wrapper.with do assert_raises Timeout::Error do Thread.new do wrapper.with { flunk 'connection checked out :(' } end.join end end assert Thread.new { wrapper.with { } }.join end class ConnWithEval def eval(arg) "eval'ed #{arg}" end end def test_wrapper_kernel_methods wrapper = ConnectionPool::Wrapper.new(timeout: 0, size: 1) { ConnWithEval.new } assert_equal "eval'ed 1", wrapper.eval(1) end def test_wrapper_with_connection_pool recorder = Recorder.new pool = ConnectionPool.new(size: 1) { recorder } wrapper = ConnectionPool::Wrapper.new(pool: pool) pool.with { |r| r.do_work('with') } wrapper.do_work('wrapped') assert_equal ['with', 'wrapped'], recorder.calls end def test_stats_without_active_connection pool = ConnectionPool.new(size: 2) { NetworkConnection.new } assert_equal(2, pool.size) assert_equal(2, pool.available) end def test_stats_with_active_connection pool = ConnectionPool.new(size: 2) { NetworkConnection.new } pool.with do assert_equal(1, pool.available) end end end connection_pool-2.2.2/README.md0000644000004100000410000000632113312077323016217 0ustar www-datawww-dataconnection\_pool ================= [![Build Status](https://travis-ci.org/mperham/connection_pool.svg)](https://travis-ci.org/mperham/connection_pool) Generic connection pooling for Ruby. MongoDB has its own connection pool. ActiveRecord has its own connection pool. This is a generic connection pool that can be used with anything, e.g. Redis, Dalli and other Ruby network clients. Usage ----- Create a pool of objects to share amongst the fibers or threads in your Ruby application: ``` ruby $memcached = ConnectionPool.new(size: 5, timeout: 5) { Dalli::Client.new } ``` Then use the pool in your application: ``` ruby $memcached.with do |conn| conn.get('some-count') end ``` If all the objects in the connection pool are in use, `with` will block until one becomes available. If no object is available within `:timeout` seconds, `with` will raise a `Timeout::Error`. Optionally, you can specify a timeout override using the with-block semantics: ``` ruby $memcached.with(timeout: 2.0) do |conn| conn.get('some-count') end ``` This will only modify the resource-get timeout for this particular invocation. This is useful if you want to fail-fast on certain non critical sections when a resource is not available, or conversely if you are comfortable blocking longer on a particular resource. This is not implemented in the below `ConnectionPool::Wrapper` class. ## Migrating to a Connection Pool You can use `ConnectionPool::Wrapper` to wrap a single global connection, making it easier to migrate existing connection code over time: ``` ruby $redis = ConnectionPool::Wrapper.new(size: 5, timeout: 3) { Redis.connect } $redis.sadd('foo', 1) $redis.smembers('foo') ``` The wrapper uses `method_missing` to checkout a connection, run the requested method and then immediately check the connection back into the pool. It's **not** high-performance so you'll want to port your performance sensitive code to use `with` as soon as possible. ``` ruby $redis.with do |conn| conn.sadd('foo', 1) conn.smembers('foo') end ``` Once you've ported your entire system to use `with`, you can simply remove `Wrapper` and use the simpler and faster `ConnectionPool`. ## Shutdown You can shut down a ConnectionPool instance once it should no longer be used. Further checkout attempts will immediately raise an error but existing checkouts will work. ```ruby cp = ConnectionPool.new { Redis.new } cp.shutdown { |conn| conn.quit } ``` Shutting down a connection pool will block until all connections are checked in and closed. **Note that shutting down is completely optional**; Ruby's garbage collector will reclaim unreferenced pools under normal circumstances. Notes ----- - Connections are lazily created as needed. - There is no provision for repairing or checking the health of a connection; connections should be self-repairing. This is true of the Dalli and Redis clients. - **WARNING**: Don't ever use `Timeout.timeout` in your Ruby code or you will see occasional silent corruption and mysterious errors. The Timeout API is unsafe and cannot be used correctly, ever. Use proper socket timeout options as exposed by Net::HTTP, Redis, Dalli, etc. Author ------ Mike Perham, [@mperham](https://twitter.com/mperham), connection_pool-2.2.2/connection_pool.gemspec0000644000004100000410000000152313312077323021474 0ustar www-datawww-data# -*- encoding: utf-8 -*- require "./lib/connection_pool/version" Gem::Specification.new do |s| s.name = "connection_pool" s.version = ConnectionPool::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Mike Perham", "Damian Janowski"] s.email = ["mperham@gmail.com", "damian@educabilia.com"] s.homepage = "https://github.com/mperham/connection_pool" s.description = s.summary = %q{Generic connection pool for Ruby} s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.license = "MIT" s.add_development_dependency 'bundler' s.add_development_dependency 'minitest', '>= 5.0.0' s.add_development_dependency 'rake' end connection_pool-2.2.2/.gitignore0000644000004100000410000000004113312077323016721 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* connection_pool-2.2.2/LICENSE0000644000004100000410000000203713312077323015745 0ustar www-datawww-dataCopyright (c) 2011 Mike Perham Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. connection_pool-2.2.2/Rakefile0000644000004100000410000000014013312077323016376 0ustar www-datawww-datarequire 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new task :default => :test connection_pool-2.2.2/lib/0000755000004100000410000000000013312077323015504 5ustar www-datawww-dataconnection_pool-2.2.2/lib/connection_pool.rb0000644000004100000410000000671113312077323021226 0ustar www-datawww-datarequire_relative 'connection_pool/version' require_relative 'connection_pool/timed_stack' # Generic connection pool class for e.g. sharing a limited number of network connections # among many threads. Note: Connections are lazily created. # # Example usage with block (faster): # # @pool = ConnectionPool.new { Redis.new } # # @pool.with do |redis| # redis.lpop('my-list') if redis.llen('my-list') > 0 # end # # Using optional timeout override (for that single invocation) # # @pool.with(timeout: 2.0) do |redis| # redis.lpop('my-list') if redis.llen('my-list') > 0 # end # # Example usage replacing an existing connection (slower): # # $redis = ConnectionPool.wrap { Redis.new } # # def do_work # $redis.lpop('my-list') if $redis.llen('my-list') > 0 # end # # Accepts the following options: # - :size - number of connections to pool, defaults to 5 # - :timeout - amount of time to wait for a connection if none currently available, defaults to 5 seconds # class ConnectionPool DEFAULTS = {size: 5, timeout: 5} class Error < RuntimeError end def self.wrap(options, &block) Wrapper.new(options, &block) end def initialize(options = {}, &block) raise ArgumentError, 'Connection pool requires a block' unless block options = DEFAULTS.merge(options) @size = options.fetch(:size) @timeout = options.fetch(:timeout) @available = TimedStack.new(@size, &block) @key = :"current-#{@available.object_id}" @key_count = :"current-#{@available.object_id}-count" end if Thread.respond_to?(:handle_interrupt) # MRI def with(options = {}) Thread.handle_interrupt(Exception => :never) do conn = checkout(options) begin Thread.handle_interrupt(Exception => :immediate) do yield conn end ensure checkin end end end else # jruby 1.7.x def with(options = {}) conn = checkout(options) begin yield conn ensure checkin end end end def checkout(options = {}) if ::Thread.current[@key] ::Thread.current[@key_count]+= 1 ::Thread.current[@key] else ::Thread.current[@key_count]= 1 ::Thread.current[@key]= @available.pop(options[:timeout] || @timeout) end end def checkin if ::Thread.current[@key] if ::Thread.current[@key_count] == 1 @available.push(::Thread.current[@key]) ::Thread.current[@key]= nil else ::Thread.current[@key_count]-= 1 end else raise ConnectionPool::Error, 'no connections are checked out' end nil end def shutdown(&block) @available.shutdown(&block) end # Size of this connection pool def size @size end # Number of pool entries available for checkout at this instant. def available @available.length end private class Wrapper < ::BasicObject METHODS = [:with, :pool_shutdown] def initialize(options = {}, &block) @pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) } end def with(&block) @pool.with(&block) end def pool_shutdown(&block) @pool.shutdown(&block) end def pool_size @pool.size end def pool_available @pool.available end def respond_to?(id, *args) METHODS.include?(id) || with { |c| c.respond_to?(id, *args) } end def method_missing(name, *args, &block) with do |connection| connection.send(name, *args, &block) end end end end connection_pool-2.2.2/lib/connection_pool/0000755000004100000410000000000013312077323020674 5ustar www-datawww-dataconnection_pool-2.2.2/lib/connection_pool/monotonic_time.rb0000644000004100000410000000312013312077323024240 0ustar www-datawww-data# Global monotonic clock from Concurrent Ruby 1.0. # Copyright (c) Jerry D'Antonio -- released under the MIT license. # Slightly modified; used with permission. # https://github.com/ruby-concurrency/concurrent-ruby require 'thread' class ConnectionPool class_definition = Class.new do if defined?(Process::CLOCK_MONOTONIC) # @!visibility private def get_time Process.clock_gettime(Process::CLOCK_MONOTONIC) end elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' # @!visibility private def get_time java.lang.System.nanoTime() / 1_000_000_000.0 end else # @!visibility private def initialize @mutex = Mutex.new @last_time = Time.now.to_f end # @!visibility private def get_time @mutex.synchronize do now = Time.now.to_f if @last_time < now @last_time = now else # clock has moved back in time @last_time += 0.000_001 end end end end end ## # Clock that cannot be set and represents monotonic time since # some unspecified starting point. # # @!visibility private GLOBAL_MONOTONIC_CLOCK = class_definition.new private_constant :GLOBAL_MONOTONIC_CLOCK class << self ## # Returns the current time a tracked by the application monotonic clock. # # @return [Float] The current monotonic time when `since` not given else # the elapsed monotonic time between `since` and the current time def monotonic_time GLOBAL_MONOTONIC_CLOCK.get_time end end end connection_pool-2.2.2/lib/connection_pool/version.rb0000644000004100000410000000005513312077323022706 0ustar www-datawww-dataclass ConnectionPool VERSION = "2.2.2" end connection_pool-2.2.2/lib/connection_pool/timed_stack.rb0000644000004100000410000001040513312077323023510 0ustar www-datawww-datarequire 'thread' require 'timeout' require_relative 'monotonic_time' ## # Raised when you attempt to retrieve a connection from a pool that has been # shut down. class ConnectionPool::PoolShuttingDownError < RuntimeError; end ## # The TimedStack manages a pool of homogeneous connections (or any resource # you wish to manage). Connections are created lazily up to a given maximum # number. # Examples: # # ts = TimedStack.new(1) { MyConnection.new } # # # fetch a connection # conn = ts.pop # # # return a connection # ts.push conn # # conn = ts.pop # ts.pop timeout: 5 # #=> raises Timeout::Error after 5 seconds class ConnectionPool::TimedStack attr_reader :max ## # Creates a new pool with +size+ connections that are created from the given # +block+. def initialize(size = 0, &block) @create_block = block @created = 0 @que = [] @max = size @mutex = Mutex.new @resource = ConditionVariable.new @shutdown_block = nil end ## # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be # used by subclasses that extend TimedStack. def push(obj, options = {}) @mutex.synchronize do if @shutdown_block @shutdown_block.call(obj) else store_connection obj, options end @resource.broadcast end end alias_method :<<, :push ## # Retrieves a connection from the stack. If a connection is available it is # immediately returned. If no connection is available within the given # timeout a Timeout::Error is raised. # # +:timeout+ is the only checked entry in +options+ and is preferred over # the +timeout+ argument (which will be removed in a future release). Other # options may be used by subclasses that extend TimedStack. def pop(timeout = 0.5, options = {}) options, timeout = timeout, 0.5 if Hash === timeout timeout = options.fetch :timeout, timeout deadline = ConnectionPool.monotonic_time + timeout @mutex.synchronize do loop do raise ConnectionPool::PoolShuttingDownError if @shutdown_block return fetch_connection(options) if connection_stored?(options) connection = try_create(options) return connection if connection to_wait = deadline - ConnectionPool.monotonic_time raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end end ## # Shuts down the TimedStack which prevents connections from being checked # out. The +block+ is called once for each connection on the stack. def shutdown(&block) raise ArgumentError, "shutdown must receive a block" unless block_given? @mutex.synchronize do @shutdown_block = block @resource.broadcast shutdown_connections end end ## # Returns +true+ if there are no available connections. def empty? (@created - @que.length) >= @max end ## # The number of connections available on the stack. def length @max - @created + @que.length end private ## # This is an extension point for TimedStack and is called with a mutex. # # This method must returns true if a connection is available on the stack. def connection_stored?(options = nil) !@que.empty? end ## # This is an extension point for TimedStack and is called with a mutex. # # This method must return a connection from the stack. def fetch_connection(options = nil) @que.pop end ## # This is an extension point for TimedStack and is called with a mutex. # # This method must shut down all connections on the stack. def shutdown_connections(options = nil) while connection_stored?(options) conn = fetch_connection(options) @shutdown_block.call(conn) end end ## # This is an extension point for TimedStack and is called with a mutex. # # This method must return +obj+ to the stack. def store_connection(obj, options = nil) @que.push obj end ## # This is an extension point for TimedStack and is called with a mutex. # # This method must create a connection if and only if the total number of # connections allowed has not been met. def try_create(options = nil) unless @created == @max object = @create_block.call @created += 1 object end end end connection_pool-2.2.2/Changes.md0000644000004100000410000000554613312077323016642 0ustar www-datawww-dataconnection\_pool changelog --------------------------- 2.2.2 ------ - Add pool `size` and `available` accessors for metrics and monitoring purposes [#97, robholland] 2.2.1 ------ - Allow CP::Wrapper to use an existing pool [#87, etiennebarrie] - Use monotonic time for more accurate timeouts [#84, jdantonio] 2.2.0 ------ - Rollback `Timeout` handling introduced in 2.1.1 and 2.1.2. It seems impossible to safely work around the issue. Please never, ever use `Timeout.timeout` in your code or you will see rare but mysterious bugs. [#75] 2.1.3 ------ - Don't increment created count until connection is successfully created. [mylesmegyesi, #73] 2.1.2 ------ - The connection\_pool will now close any connections which respond to `close` (Dalli) or `disconnect!` (Redis). This ensures discarded connections from the fix in 2.1.1 are torn down ASAP and don't linger open. 2.1.1 ------ - Work around a subtle race condition with code which uses `Timeout.timeout` and checks out a connection within the timeout block. This might cause connections to get into a bad state and raise very odd errors. [tamird, #67] 2.1.0 ------ - Refactoring to better support connection pool subclasses [drbrain, #55] - `with` should return value of the last expression [#59] 2.0.0 ----- - The connection pool is now lazy. Connections are created as needed and retained until the pool is shut down. [drbrain, #52] 1.2.0 ----- - Add `with(options)` and `checkout(options)`. [mattcamuto] Allows the caller to override the pool timeout. ```ruby @pool.with(:timeout => 2) do |conn| end ``` 1.1.0 ----- - New `#shutdown` method (simao) This method accepts a block and calls the block for each connection in the pool. After calling this method, trying to get a connection from the pool raises `PoolShuttingDownError`. 1.0.0 ----- - `#with_connection` is now gone in favor of `#with`. - We no longer pollute the top level namespace with our internal `TimedStack` class. 0.9.3 -------- - `#with_connection` is now deprecated in favor of `#with`. A warning will be issued in the 0.9 series and the method will be removed in 1.0. - We now reuse objects when possible. This means that under no contention, the same object will be checked out from the pool after subsequent calls to `ConnectionPool#with`. This change should have no impact on end user performance. If anything, it should be an improvement, depending on what objects you are pooling. 0.9.2 -------- - Fix reentrant checkout leading to early checkin. 0.9.1 -------- - Fix invalid superclass in version.rb 0.9.0 -------- - Move method\_missing magic into ConnectionPool::Wrapper (djanowski) - Remove BasicObject superclass (djanowski) 0.1.0 -------- - More precise timeouts and better error message - ConnectionPool now subclasses BasicObject so `method_missing` is more effective. connection_pool-2.2.2/Gemfile0000644000004100000410000000010413312077323016224 0ustar www-datawww-datasource "https://rubygems.org" gemspec(development_group: :runtime)