HotTub Build Status Coverage Status

Flexible, thread-safe, connection pooling for Ruby. Configurable for any client you desire with built in support for Net::HTTP and Excon.

Requirements

HotTub is tested on MRI, JRUBY and Rubinius

  • Ruby >= 2.0 # Although older versions may work

Installation

HotTub is available through Rubygems and can be installed via:

$ gem install hot_tub

Rails setup

Add hot_tub to your gemfile:

gem 'hot_tub'

Run bundle:

bundle install

Configure Logger by creating config\initializers\hot_tub.rb and adding the following:

HotTub.logger = Rails.logger

Usage

Pools Managed by HotTub

HotTub::Sessions are used to manage multiple pools with a single object and using a single reaper. A global Sessions object is available from the HotTub module and has several helper methods.

require 'hot_tub'

# Lets configure HotTub global sessions to use NetHTTP as our default client

HotTub.default_client = lambda { |url| 
  uri = URI.parse(url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.start
  http 
}

# Add a HotTub::Pool for "https://www.google.com" and use it.
HotTub.run("https://www.google.com") do |clnt|    
  puts clnt.get('/').code
end

# Re-uses the previously defined pool
HotTub.run("https://www.google.com") do |clnt|    
  puts clnt.get('/').code
end

# Add another HotTub::Pool for "https://www.yahoo.com" and use it
HotTub.run("https://www.yahoo.com") do |clnt|    
  puts clnt.get('/').code
end

# We can add more HotTub::Pools with unique settings.
# Lets add another HotTub::Pool of Excon clients with a pool size of 12.
# HotTub.stage sets the options passed to a settings cache, the pool is
# created the first time we call HotTub.run. We are not setting :max_size 
# so our connections will grow to match our currency. Once load dies down 
# our pool will be reaped back down to 12 connections

HotTub.stage('excon_yahoo', { :size => 12} ) do
 Excon.new("https://yahoo.com", :thread_safe_sockets => false )
end

# Lets add Redis too. HotTub.add returns the pool created for that key so we
# can store that in an constant for easy access.
# We don't want too many connections so we set our :max_size. Under load our pool
# can grow to 30 connections. Once load dies down our pool can be reaped back down to 5

REDIS = HotTub.add("redis", :size => 5, :max_size => 30) { Redis.new } 

# Now we can call any of our pools using the key we set.

HotTub.run('excon_yahoo') do |clnt|    
  puts clnt.get.status
end

# Since our REDIS constant was set to HotTub::Pool instance return from HotTub.add 
# we do not need the key when calling #run
REDIS.run do |clnt|
  clnt.set('hot', 'stuff')
end

# Re-use "https://www.google.com" we created earlier
HotTub.run("https://www.google.com") do |clnt|    
  puts clnt.get('/').code
end

Single Pool

pool = HotTub::Pool.new(:size => 5, :max_size => 30, :reap_timeout => 60) { Redis.new }

pool.run |clnt|
 clnt.set('hot', 'stuff')
end

pool.run |clnt|
  pool.get('hot')
end

Connection Life Cycles

HotTub has built in support for closing NetHTTP and Excon. If you need more control or have a different library you would like to use, HotTub can be configured to support your needs using :close, :clean, and :reap? options in a pools settings, and each option can accept a lambda that accepts the client as an argument or symbol representing a method to call on the client.

:close is used to define how a connections should be closed at shutdown and upon reaping.

:clean is called on each existing connection as its pulled from the pool.

:reap? is used to determine if a connection in the pool is ready for reaping.

pool_options = {
  :size     => 5
  :max_size => 10
  :close    => :close
  :clean    => lambda { |clnt| clnt.reconnect if clnt.dirty? },
  :reap?    => :stale? # returns truthy if we want to reap
}

HotTub.add('offBrand', pool_options) { MyCoolClient.new }
# or
HotTub::Pool.new(pool_options){ MyCoolClient.new }

Forking

HotTub::Pool automatically detects forks and drains the pool, so no additional "after fork" code is required.

Contributing to HotTub

  • Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
  • Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
  • Fork the project
  • Start a feature/bugfix branch
  • Commit and push until you are happy with your contribution
  • Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
  • Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.