class ConnectionPool::TimedStack

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(size: 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

Attributes

max[R]

Public Class Methods

new(size: 0, &block) click to toggle source

Creates a new pool with size connections that are created from the given block.

# File lib/connection_pool/timed_stack.rb, line 25
def initialize(size: 0, &block)
  @create_block = block
  @created = 0
  @que = []
  @max = size
  @mutex = Thread::Mutex.new
  @resource = Thread::ConditionVariable.new
  @shutdown_block = nil
end

Public Instance Methods

<<(obj, **)
Alias for: push
decrement_created() click to toggle source

Reduce the created count

# File lib/connection_pool/timed_stack.rb, line 143
def decrement_created
  @created -= 1 unless @created == 0
end
empty?() click to toggle source

Returns true if there are no available connections.

# File lib/connection_pool/timed_stack.rb, line 125
def empty?
  (@created - @que.length) >= @max
end
idle() click to toggle source

The number of connections created and available on the stack.

# File lib/connection_pool/timed_stack.rb, line 137
def idle
  @que.length
end
length() click to toggle source

The number of connections available on the stack.

# File lib/connection_pool/timed_stack.rb, line 131
def length
  @max - @created + @que.length
end
pop(timeout: 0.5, exception: ConnectionPool::TimeoutError, **) click to toggle source

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.

@option options [Float] :timeout (0.5) Wait this many seconds for an available entry @option options [Class] :exception (ConnectionPool::TimeoutError) Exception class to raise

if an entry was not available within the timeout period. Use `exception: false` to return nil.

Other options may be used by subclasses that extend TimedStack.

# File lib/connection_pool/timed_stack.rb, line 62
def pop(timeout: 0.5, exception: ConnectionPool::TimeoutError, **)
  deadline = current_time + timeout
  @mutex.synchronize do
    loop do
      raise ConnectionPool::PoolShuttingDownError if @shutdown_block
      if (conn = try_fetch_connection(**))
        return conn
      end

      connection = try_create(**)
      return connection if connection

      to_wait = deadline - current_time
      if to_wait <= 0
        if exception
          raise exception, "Waited #{timeout} sec, #{length}/#{@max} available"
        else
          return nil
        end
      end
      @resource.wait(@mutex, to_wait)
    end
  end
end
push(obj, **) click to toggle source

Returns obj to the stack. Additional kwargs are ignored in TimedStack but may be used by subclasses that extend TimedStack.

# File lib/connection_pool/timed_stack.rb, line 38
def push(obj, **)
  @mutex.synchronize do
    if @shutdown_block
      @created -= 1 unless @created == 0
      @shutdown_block.call(obj)
    else
      store_connection obj, **
    end

    @resource.broadcast
  end
end
Also aliased as: <<
reap(idle_seconds:) { |conn| ... } click to toggle source

Reaps connections that were checked in more than idle_seconds ago.

# File lib/connection_pool/timed_stack.rb, line 106
def reap(idle_seconds:)
  raise ArgumentError, "reap must receive a block" unless block_given?
  raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
  raise ConnectionPool::PoolShuttingDownError if @shutdown_block

  count = idle
  count.times do
    conn = @mutex.synchronize do
      raise ConnectionPool::PoolShuttingDownError if @shutdown_block
      reserve_idle_connection(idle_seconds)
    end
    break unless conn

    yield conn
  end
end
shutdown(reload: false, &block) click to toggle source

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.

# File lib/connection_pool/timed_stack.rb, line 92
def shutdown(reload: false, &block)
  raise ArgumentError, "shutdown must receive a block" unless block

  @mutex.synchronize do
    @shutdown_block = block
    @resource.broadcast

    shutdown_connections
    @shutdown_block = nil if reload
  end
end

Private Instance Methods

connection_stored?(**) click to toggle source

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.

# File lib/connection_pool/timed_stack.rb, line 167
def connection_stored?(**)
  !@que.empty?
end
current_time() click to toggle source
# File lib/connection_pool/timed_stack.rb, line 149
def current_time
  Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
fetch_connection(**) click to toggle source

This is an extension point for TimedStack and is called with a mutex.

This method must return a connection from the stack.

# File lib/connection_pool/timed_stack.rb, line 175
def fetch_connection(**)
  @que.pop&.first
end
idle_connections?(idle_seconds) click to toggle source

This is an extension point for TimedStack and is called with a mutex.

Returns true if the first connection in the stack has been idle for more than idle_seconds

# File lib/connection_pool/timed_stack.rb, line 209
def idle_connections?(idle_seconds)
  return unless connection_stored?
  # Most idle will be at the head so `first`
  age = (current_time - @que.first.last)
  age > idle_seconds
end
reserve_idle_connection(idle_seconds) click to toggle source

This is an extension point for TimedStack and is called with a mutex.

This method returns the oldest idle connection if it has been idle for more than idle_seconds. This requires that the stack is kept in order of checked in time (oldest first).

# File lib/connection_pool/timed_stack.rb, line 195
def reserve_idle_connection(idle_seconds)
  return unless idle_connections?(idle_seconds)

  @created -= 1 unless @created == 0

  # Most active elements are at the tail of the array.
  # Most idle will be at the head so `shift` rather than `pop`.
  @que.shift.first
end
shutdown_connections(**) click to toggle source

This is an extension point for TimedStack and is called with a mutex.

This method must shut down all connections on the stack.

# File lib/connection_pool/timed_stack.rb, line 183
def shutdown_connections(**)
  while (conn = try_fetch_connection(**))
    @created -= 1 unless @created == 0
    @shutdown_block.call(conn)
  end
end
store_connection(obj, **) click to toggle source

This is an extension point for TimedStack and is called with a mutex.

This method must return obj to the stack.

# File lib/connection_pool/timed_stack.rb, line 220
def store_connection(obj, **)
  @que.push [obj, current_time]
end
try_create(**) click to toggle source

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.

# File lib/connection_pool/timed_stack.rb, line 229
def try_create(**)
  unless @created == @max
    object = @create_block.call
    @created += 1
    object
  end
end
try_fetch_connection(**) click to toggle source

This is an extension point for TimedStack and is called with a mutex.

This method must returns a connection from the stack if one exists. Allows subclasses with expensive match/search algorithms to avoid double-handling their stack.

# File lib/connection_pool/timed_stack.rb, line 159
def try_fetch_connection(**)
  connection_stored?(**) && fetch_connection(**)
end