connection_pool-2.2.5/0000755000004100000410000000000014041725274014746 5ustar www-datawww-dataconnection_pool-2.2.5/test/0000755000004100000410000000000014041725274015725 5ustar www-datawww-dataconnection_pool-2.2.5/test/test_connection_pool_timed_stack.rb0000644000004100000410000000511014041725274025045 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(ConnectionPool::TimeoutError) { @stack.pop timeout: 0 } assert_equal "Waited 0 sec", e.message end def test_pop_empty_2_0_compatibility e = assert_raises(Timeout::Error) { @stack.pop 0 } 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 { @stack.pop } 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_pop_shutdown_reload stack = ConnectionPool::TimedStack.new(1) { Object.new } object = stack.pop stack.push(object) stack.shutdown(reload: true) {} refute_equal object, stack.pop 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.5/test/helper.rb0000644000004100000410000000017514041725274017534 0ustar www-datawww-datagem "minitest" require "minitest/pride" require "minitest/autorun" $VERBOSE = 1 require_relative "../lib/connection_pool" connection_pool-2.2.5/test/test_connection_pool.rb0000644000004100000410000003117414041725274022507 0ustar www-datawww-datarequire_relative "helper" class TestConnectionPool < Minitest::Test class NetworkConnection SLEEP_TIME = 0.1 def initialize @x = 0 end def do_something(*_args, increment: 1) @x += increment sleep SLEEP_TIME @x end def do_something_with_positional_hash(options) @x += options[:increment] || 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) { Thread.new do pool.with { sleep } 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) { Thread.new do pool.with do |net| net.do_something 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 { pool.with do |net| net.do_something sleep 0.01 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 Thread.new { assert_raises Timeout::Error do pool.checkout end }.join end assert Thread.new { pool.checkout }.join end def test_then pool = ConnectionPool.new { Object.new } assert_equal pool.method(:then), pool.method(:with) 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.available sleep 0.015 end end end assert_equal 1, pool.available end def test_invalid_size assert_raises ArgumentError, TypeError do ConnectionPool.new(timeout: 0, size: nil) { Object.new } end assert_raises ArgumentError, TypeError do ConnectionPool.new(timeout: 0, size: "") { Object.new } end end def test_handle_interrupt_ensures_checkin pool = ConnectionPool.new(timeout: 0, size: 1) { Object.new } def pool.checkout(options) sleep 0.015 super end did_something = false action = lambda 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 if RUBY_ENGINE == "ruby" # These asserts rely on the Ruby implementation reaching `did_something = # true` before the interrupt is detected by the thread. Interrupt # detection timing is implementation-specific in practice, with JRuby, # Rubinius, and TruffleRuby all having different interrupt timings to MRI. # In fact they generally detect interrupts more quickly than MRI, so they # may not reach `did_something = true` before detecting the interrupt. assert_raises Timeout::Error, &action assert did_something else action.call end assert_equal 1, pool.available end def test_explicit_return pool = ConnectionPool.new(timeout: 0, size: 1) { mock = Minitest::Mock.new def mock.disconnect! raise "should not disconnect upon explicit return" end mock } 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 { pool.with do |net| net.do_something sleep 0.01 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 Thread.new { assert_raises Timeout::Error do pool.checkout end }.join 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) { pool.checkin } 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 Thread.new { assert_raises Timeout::Error do pool.checkout end }.join 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 { pool.checkout } 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 { pool.with do |net| net.do_something sleep 0.01 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 } assert_equal 8, pool.do_something(increment: 2) assert_equal 10, pool.do_something_with_positional_hash({ increment: 2, symbol_key: 3, "string_key" => 4 }) 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 { |net| net.fast } assert_equal 1, result end def test_heavy_threading pool = ConnectionPool.new(timeout: 0.5, size: 3) { NetworkConnection.new } threads = Array.new(20) { Thread.new do pool.with do |net| sleep 0.01 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 { pool.with { |c| c.object_id } } 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 { |t| pool.with do |r_other| r_other.do_work("other") 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) { Recorder.new.tap { |r| recorders << r } } 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) { Recorder.new.tap { |r| recorders << r } } 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) { Recorder.new.tap { |r| recorders << r } } 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_wrapped_pool wrapper = ConnectionPool::Wrapper.new { NetworkConnection.new } assert_equal ConnectionPool, wrapper.wrapped_pool.class 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 Thread.new { assert_raises Timeout::Error do wrapper.with { flunk "connection checked out :(" } end }.join 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 def test_stats_with_string_size pool = ConnectionPool.new(size: "2") { NetworkConnection.new } pool.with do assert_equal(2, pool.size) assert_equal(1, pool.available) end end end connection_pool-2.2.5/README.md0000644000004100000410000001026514041725274016231 0ustar www-datawww-dataconnection\_pool ================= [![Build Status](https://github.com/mperham/connection_pool/actions/workflows/ci.yml/badge.svg)](https://github.com/mperham/connection_pool/actions/workflows/ci.yml) 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`. You can also use `ConnectionPool#then` to support _both_ a connection pool and a raw client (requires Ruby 2.5+). ```ruby # Compatible with a raw Redis::Client, and ConnectionPool Redis $redis.then { |r| r.set 'foo' 'bar' } ``` 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.new } $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. ## Reload You can reload a ConnectionPool instance in the case it is desired to close all connections to the pool and, unlike `shutdown`, afterwards recreate connections so the pool may continue to be used. Reloading may be useful after forking the process. ```ruby cp = ConnectionPool.new { Redis.new } cp.reload { |conn| conn.quit } cp.with { |conn| conn.get('some-count') } ``` Like `shutdown`, this will block until all connections are checked in and closed. ## Current State There are several methods that return information about a pool. ```ruby cp = ConnectionPool.new(size: 10) { Redis.new } cp.size # => 10 cp.available # => 10 cp.with do |conn| cp.size # => 10 cp.available # => 9 end ``` 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, [@getajobmike](https://twitter.com/getajobmike), connection_pool-2.2.5/connection_pool.gemspec0000644000004100000410000000146714041725274021513 0ustar www-datawww-datarequire "./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 = "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" s.required_ruby_version = ">= 2.2.0" end connection_pool-2.2.5/.gitignore0000644000004100000410000000004114041725274016731 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* connection_pool-2.2.5/LICENSE0000644000004100000410000000203714041725274015755 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.5/Rakefile0000644000004100000410000000013514041725274016412 0ustar www-datawww-datarequire "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new task default: :test connection_pool-2.2.5/lib/0000755000004100000410000000000014041725274015514 5ustar www-datawww-dataconnection_pool-2.2.5/lib/connection_pool.rb0000644000004100000410000000643714041725274021243 0ustar www-datawww-datarequire "timeout" require "connection_pool/version" class ConnectionPool class Error < ::RuntimeError; end class PoolShuttingDownError < ::ConnectionPool::Error; end class TimeoutError < ::Timeout::Error; end end # Generic connection pool class for sharing a limited number of objects or network connections # among many threads. Note: pool elements 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} 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 = Integer(options.fetch(:size)) @timeout = options.fetch(:timeout) @available = TimedStack.new(@size, &block) @key = :"pool-#{@available.object_id}" @key_count = :"pool-#{@available.object_id}-count" end 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 alias then with 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 ::Thread.current[@key_count] = nil else ::Thread.current[@key_count] -= 1 end else raise ConnectionPool::Error, "no connections are checked out" end nil end ## # Shuts down the ConnectionPool by passing each connection to +block+ and # then removing it from the pool. Attempting to checkout a connection after # shutdown will raise +ConnectionPool::PoolShuttingDownError+. def shutdown(&block) @available.shutdown(&block) end ## # Reloads the ConnectionPool by passing each connection to +block+ and then # removing it the pool. Subsequent checkouts will create new connections as # needed. def reload(&block) @available.shutdown(reload: true, &block) end # Size of this connection pool attr_reader :size # Number of pool entries available for checkout at this instant. def available @available.length end end require "connection_pool/timed_stack" require "connection_pool/wrapper" connection_pool-2.2.5/lib/connection_pool/0000755000004100000410000000000014041725274020704 5ustar www-datawww-dataconnection_pool-2.2.5/lib/connection_pool/version.rb0000644000004100000410000000005514041725274022716 0ustar www-datawww-dataclass ConnectionPool VERSION = "2.2.5" end connection_pool-2.2.5/lib/connection_pool/wrapper.rb0000644000004100000410000000252414041725274022714 0ustar www-datawww-dataclass ConnectionPool class Wrapper < ::BasicObject METHODS = [:with, :pool_shutdown, :wrapped_pool] def initialize(options = {}, &block) @pool = options.fetch(:pool) { ::ConnectionPool.new(options, &block) } end def wrapped_pool @pool 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 # rubocop:disable Style/MethodMissingSuper # rubocop:disable Style/MissingRespondToMissing if ::RUBY_VERSION >= "3.0.0" def method_missing(name, *args, **kwargs, &block) with do |connection| connection.send(name, *args, **kwargs, &block) end end elsif ::RUBY_VERSION >= "2.7.0" ruby2_keywords def method_missing(name, *args, &block) with do |connection| connection.send(name, *args, &block) end end else def method_missing(name, *args, &block) with do |connection| connection.send(name, *args, &block) end end end # rubocop:enable Style/MethodMissingSuper # rubocop:enable Style/MissingRespondToMissing end end connection_pool-2.2.5/lib/connection_pool/timed_stack.rb0000644000004100000410000001042514041725274023522 0ustar www-datawww-data## # 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 ConnectionPool::TimeoutError 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 << 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 ConnectionPool::TimeoutError 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 = current_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 - current_time raise ConnectionPool::TimeoutError, "Waited #{timeout} sec" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end end ## # Shuts down the TimedStack by passing each connection to +block+ and then # removing it from the pool. Attempting to checkout a connection after # shutdown will raise +ConnectionPool::PoolShuttingDownError+ unless # +:reload+ is +true+. def shutdown(reload: false, &block) raise ArgumentError, "shutdown must receive a block" unless block_given? @mutex.synchronize do @shutdown_block = block @resource.broadcast shutdown_connections @shutdown_block = nil if reload 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 def current_time Process.clock_gettime(Process::CLOCK_MONOTONIC) end ## # 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 @created = 0 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.5/Changes.md0000644000004100000410000000653614041725274016652 0ustar www-datawww-data# connection_pool Changelog 2.2.5 ------ - Fix argument forwarding on Ruby 2.7 [#149] 2.2.4 ------ - Add `reload` to close all connections, recreating them afterwards [Andrew Marshall, #140] - Add `then` as a way to use a pool or a bare connection with the same code path [#138] 2.2.3 ------ - Pool now throws `ConnectionPool::TimeoutError` on timeout. [#130] - Use monotonic clock present in all modern Rubies [Tero Tasanen, #109] - Remove code hacks necessary for JRuby 1.7 - Expose wrapped pool from ConnectionPool::Wrapper [Thomas Lecavelier, #113] 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.5/Gemfile0000644000004100000410000000010414041725274016234 0ustar www-datawww-datasource "https://rubygems.org" gemspec(development_group: :runtime) connection_pool-2.2.5/.github/0000755000004100000410000000000014041725274016306 5ustar www-datawww-dataconnection_pool-2.2.5/.github/workflows/0000755000004100000410000000000014041725274020343 5ustar www-datawww-dataconnection_pool-2.2.5/.github/workflows/ci.yml0000644000004100000410000000111414041725274021456 0ustar www-datawww-dataname: CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest continue-on-error: ${{ matrix.experimental }} strategy: fail-fast: false matrix: ruby: ["2.4", "2.5", "2.6", "2.7", "3.0", "jruby"] experimental: [false] include: - ruby: "truffleruby" experimental: true steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{matrix.ruby}} bundler-cache: true - name: Run tests timeout-minutes: 5 run: ${{matrix.env}} bundle exec rake