pax_global_header00006660000000000000000000000064121455070320014511gustar00rootroot0000000000000052 comment=b78328d660d3d750926fe683b5d8385fcbeb8e54 ruby-connection-pool-1.0.0/000077500000000000000000000000001214550703200155745ustar00rootroot00000000000000ruby-connection-pool-1.0.0/.gitignore000066400000000000000000000000411214550703200175570ustar00rootroot00000000000000*.gem .bundle Gemfile.lock pkg/* ruby-connection-pool-1.0.0/Changes.md000066400000000000000000000020521214550703200174650ustar00rootroot000000000000001.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. ruby-connection-pool-1.0.0/Gemfile000066400000000000000000000001761214550703200170730ustar00rootroot00000000000000source "http://rubygems.org" # Specify your gem's dependencies in connection_pool.gemspec gemspec gem 'rake' gem 'minitest' ruby-connection-pool-1.0.0/LICENSE000066400000000000000000000020371214550703200166030ustar00rootroot00000000000000Copyright (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. ruby-connection-pool-1.0.0/README.md000066400000000000000000000036251214550703200170610ustar00rootroot00000000000000connection_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. Install ------------ gem install connection_pool Notes ------------ - Connections are eager created when the pool is created. - 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. 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 |dalli| dalli.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 use `ConnectionPool::Wrapper` to wrap a single global connection, making it easier to port your 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 a simple, fast ConnectionPool. Author -------------- Mike Perham, [@mperham](https://twitter.com/mperham), ruby-connection-pool-1.0.0/Rakefile000066400000000000000000000003741214550703200172450ustar00rootroot00000000000000begin require 'bundler' Bundler::GemHelper.install_tasks rescue LoadError end require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'test' test.warning = true test.pattern = 'test/**/test_*.rb' end task :default => :test ruby-connection-pool-1.0.0/connection_pool.gemspec000066400000000000000000000012161214550703200223310ustar00rootroot00000000000000# -*- 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"] s.email = ["mperham@gmail.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"] end ruby-connection-pool-1.0.0/lib/000077500000000000000000000000001214550703200163425ustar00rootroot00000000000000ruby-connection-pool-1.0.0/lib/connection_pool.rb000066400000000000000000000042411214550703200220600ustar00rootroot00000000000000require_relative 'connection_pool/version' # Generic connection pool class for e.g. sharing a limited number of network connections # among many threads. Note: Connections are eager 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 # # 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 = options.fetch(:size) @timeout = options.fetch(:timeout) @available = TimedStack.new(@size, &block) @key = :"current-#{@available.object_id}" end def with conn = checkout begin yield conn ensure checkin end end def checkout stack = ::Thread.current[@key] ||= [] if stack.empty? conn = @available.pop(@timeout) else conn = stack.last end stack.push conn conn end def checkin stack = ::Thread.current[@key] conn = stack.pop if stack.empty? @available << conn end nil end class Wrapper < ::BasicObject METHODS = [:with] def initialize(options = {}, &block) @pool = ::ConnectionPool.new(options, &block) end def with yield @pool.checkout ensure @pool.checkin end def respond_to?(id, *args) METHODS.include?(id) || @pool.with { |c| c.respond_to?(id, *args) } end def method_missing(name, *args, &block) @pool.with do |connection| connection.send(name, *args, &block) end end end end require_relative 'connection_pool/timed_stack' ruby-connection-pool-1.0.0/lib/connection_pool/000077500000000000000000000000001214550703200215325ustar00rootroot00000000000000ruby-connection-pool-1.0.0/lib/connection_pool/timed_stack.rb000066400000000000000000000013601214550703200243460ustar00rootroot00000000000000require 'thread' require 'timeout' class ConnectionPool::TimedStack def initialize(size = 0) @que = Array.new(size) { yield } @mutex = Mutex.new @resource = ConditionVariable.new end def push(obj) @mutex.synchronize do @que.push obj @resource.broadcast end end alias_method :<<, :push def pop(timeout=0.5) deadline = Time.now + timeout @mutex.synchronize do loop do return @que.pop unless @que.empty? to_wait = deadline - Time.now raise Timeout::Error, "Waited #{timeout} sec" if to_wait <= 0 @resource.wait(@mutex, to_wait) end end end def empty? @que.empty? end def clear @que.clear end def length @que.length end end ruby-connection-pool-1.0.0/lib/connection_pool/version.rb000066400000000000000000000000551214550703200235440ustar00rootroot00000000000000class ConnectionPool VERSION = "1.0.0" end ruby-connection-pool-1.0.0/metadata.yml000066400000000000000000000023511214550703200201000ustar00rootroot00000000000000--- !ruby/object:Gem::Specification name: connection_pool version: !ruby/object:Gem::Version version: 1.0.0 prerelease: platform: ruby authors: - Mike Perham autorequire: bindir: bin cert_chain: [] date: 2012-12-19 00:00:00.000000000 Z dependencies: [] description: Generic connection pool for Ruby email: - mperham@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - Changes.md - Gemfile - LICENSE - README.md - Rakefile - connection_pool.gemspec - lib/connection_pool.rb - lib/connection_pool/timed_stack.rb - lib/connection_pool/version.rb - test/helper.rb - test/test_connection_pool.rb homepage: https://github.com/mperham/connection_pool licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.24 signing_key: specification_version: 3 summary: Generic connection pool for Ruby test_files: - test/helper.rb - test/test_connection_pool.rb ruby-connection-pool-1.0.0/test/000077500000000000000000000000001214550703200165535ustar00rootroot00000000000000ruby-connection-pool-1.0.0/test/helper.rb000066400000000000000000000004531214550703200203610ustar00rootroot00000000000000require 'rubygems' require 'minitest/pride' require 'minitest/autorun' puts RUBY_DESCRIPTION class MiniTest::Unit::TestCase def async_test(time=0.5) q = TimedQueue.new yield Proc.new { q << nil } q.timed_pop(time) end end $VERBOSE = 1 require_relative '../lib/connection_pool' ruby-connection-pool-1.0.0/test/test_connection_pool.rb000066400000000000000000000061321214550703200233310ustar00rootroot00000000000000Thread.abort_on_exception = true require 'helper' class TestConnectionPool < MiniTest::Unit::TestCase class NetworkConnection def initialize @x = 0 end def do_something @x += 1 sleep 0.05 @x end def fast @x += 1 end def do_something_with_block @x += yield sleep 0.05 @x end def respond_to?(method_id, *args) method_id == :do_magic || super(method_id, *args) end end def test_basic_multithreaded_usage pool = ConnectionPool.new(:size => 5) { NetworkConnection.new } threads = [] 15.times do threads << Thread.new do pool.with do |net| net.do_something end end end a = Time.now result = threads.map(&:value) b = Time.now assert_operator((b - a), :>, 0.125) assert_equal([1,2,3].cycle(5).sort, result.sort) end def test_timeout pool = ConnectionPool.new(:timeout => 0.05, :size => 1) { NetworkConnection.new } Thread.new do pool.with do |net| net.do_something sleep 0.1 end end sleep 0.05 assert_raises Timeout::Error do pool.with { |net| net.do_something } end sleep 0.05 pool.with do |conn| refute_nil conn end end def test_passthru pool = ConnectionPool.wrap(:timeout => 0.1, :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 => 0.1, :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 => 0.1, :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 } 20.times do Thread.new do pool.with do |net| sleep 0.05 end end end sleep 0.5 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 class Recorder def initialize @calls = [] end attr_reader :calls def do_work(label) @calls << label end 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 sleep 0.1 r_outer.do_work('outer') end @other.join assert_equal ['inner', 'outer', 'other'], recorder.calls end end