class SSHKit::Backend::ConnectionPool

The ConnectionPool caches connections and allows them to be reused, so long as the reuse happens within the `idle_timeout` period. Timed out connections are closed, forcing a new connection to be used in that case.

Additionally, a background thread is started to check for abandoned connections that have timed out without any attempt at being reused. These are eventually closed as well and removed from the cache.

If `idle_timeout` set to `false`, `0`, or `nil`, no caching is performed, and a new connection is created and then immediately closed each time. The default timeout is 30 (seconds).

There is a single public method: `with`. Example usage:

pool = SSHKit::Backend::ConnectionPool.new
pool.with(Net::SSH.method(:start), "host", "username") do |connection|
  # do stuff with connection
end

Constants

NilCache

A cache that holds no connections. Any connection provided to this cache is simply closed.

Attributes

caches[R]
idle_timeout[RW]
timed_out_connections[R]

Public Class Methods

new(idle_timeout=30) click to toggle source
# File lib/sshkit/backends/connection_pool.rb, line 44
def initialize(idle_timeout=30)
  @idle_timeout = idle_timeout
  @caches = {}
  @caches.extend(MonitorMixin)
  @timed_out_connections = Queue.new
  Thread.new { run_eviction_loop }
end

Public Instance Methods

close_connections() click to toggle source

Immediately close all cached connections and empty the pool.

# File lib/sshkit/backends/connection_pool.rb, line 73
def close_connections
  caches.synchronize do
    caches.values.each(&:clear)
    caches.clear
    process_deferred_close
  end
end
flush_connections() click to toggle source

Immediately remove all cached connections, without closing them. This only exists for unit test purposes.

# File lib/sshkit/backends/connection_pool.rb, line 68
def flush_connections
  caches.synchronize { caches.clear }
end
with(connection_factory, *args) { |conn| ... } click to toggle source

Creates a new connection or reuses a cached connection (if possible) and yields the connection to the given block. Connections are created by invoking the `connection_factory` proc with the given `args`. The arguments are used to construct a key used for caching.

# File lib/sshkit/backends/connection_pool.rb, line 56
def with(connection_factory, *args)
  cache = find_cache(args)
  conn = cache.pop || begin
    connection_factory.call(*args)
  end
  yield(conn)
ensure
  cache.push(conn) unless conn.nil?
end

Private Instance Methods

cache_enabled?() click to toggle source
# File lib/sshkit/backends/connection_pool.rb, line 87
def cache_enabled?
  idle_timeout && idle_timeout > 0
end
find_cache(args) click to toggle source

Look up a Cache that matches the given connection arguments.

# File lib/sshkit/backends/connection_pool.rb, line 92
def find_cache(args)
  if cache_enabled?
    key = args.to_s
    caches[key] || thread_safe_find_or_create_cache(key)
  else
    NilCache.new(method(:silently_close_connection))
  end
end
process_deferred_close() click to toggle source

Immediately close any connections that are pending closure. rubocop:disable Lint/HandleExceptions

# File lib/sshkit/backends/connection_pool.rb, line 125
def process_deferred_close
  until timed_out_connections.empty?
    connection = timed_out_connections.pop(true)
    silently_close_connection(connection)
  end
rescue ThreadError
  # Queue#pop(true) raises ThreadError if the queue is empty.
  # This could only happen if `close_connections` is called at the same time
  # the background eviction thread has woken up to close connections. In any
  # case, it is not something we need to care about, since an empty queue is
  # perfectly OK.
end
run_eviction_loop() click to toggle source

Loops indefinitely to close connections and to find abandoned connections that need to be closed.

# File lib/sshkit/backends/connection_pool.rb, line 113
def run_eviction_loop
  loop do
    process_deferred_close

    # Periodically sweep all Caches to evict stale connections
    sleep([idle_timeout, 5].min)
    caches.values.each(&:evict)
  end
end
silently_close_connection(connection) click to toggle source

Close the given `connection` immediately, assuming it responds to a `close` method. If it doesn't, or if `nil` is provided, it is silently ignored. Any `StandardError` is also silently ignored. Returns `true` if the connection was closed; `false` if it was already closed or could not be closed due to an error.

# File lib/sshkit/backends/connection_pool.rb, line 150
def silently_close_connection(connection)
  return false unless connection.respond_to?(:close)
  return false if connection.respond_to?(:closed?) && connection.closed?
  connection.close
  true
rescue StandardError
  false
end
silently_close_connection_later(connection) click to toggle source

Adds the connection to a queue that is processed asynchronously by a background thread. The connection will eventually be closed.

# File lib/sshkit/backends/connection_pool.rb, line 141
def silently_close_connection_later(connection)
  timed_out_connections << connection
end
thread_safe_find_or_create_cache(key) click to toggle source

Cache creation needs to happen in a mutex, because otherwise a race condition might cause two identical caches to be created for the same key.

# File lib/sshkit/backends/connection_pool.rb, line 103
def thread_safe_find_or_create_cache(key)
  caches.synchronize do
    caches[key] ||= begin
      Cache.new(idle_timeout, method(:silently_close_connection_later))
    end
  end
end