Class: HttpConnectionPool::Pool

Inherits:
Object
  • Object
show all
Defined in:
lib/http_connection_pool/pool.rb

Overview

Manages a pool of persistent HTTP::Session connections for a single URL origin. On http v6, HTTP.persistent returns an HTTP::Session, and http’s own README notes that a persistent session is not thread-safe on its own — it points to the ‘connection_pool` gem for thread-safe persistent use. That is exactly the pattern this class follows: each caller checks out its own Session for the duration of a request.

Backed by the ‘connection_pool` gem (>= 2.5.5), which is both thread- and Fiber.scheduler-aware: when running under a fiber scheduler, blocking checkouts yield to the scheduler instead of parking the OS thread.

Pool instances are never created directly — obtain them through Registry.

Constant Summary collapse

TimeoutError =

Backward-compatible aliases — the canonical classes live in errors.rb under HttpConnectionPool. Existing ‘rescue Pool::TimeoutError` keeps working.

HttpConnectionPool::TimeoutError
ClosedError =
HttpConnectionPool::ClosedError
DEFAULT_SIZE =
5
DEFAULT_TIMEOUT =

seconds to wait for a connection to become available

5.0

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(origin:, size: DEFAULT_SIZE, timeout: DEFAULT_TIMEOUT, **options) ⇒ Pool

Returns a new instance of Pool.

Parameters:

  • origin (String)

    canonical origin, e.g. “api.example.com:443

  • size (Integer) (defaults to: DEFAULT_SIZE)

    maximum number of concurrent connections

  • timeout (Float) (defaults to: DEFAULT_TIMEOUT)

    seconds to block waiting for a free connection

  • options (Hash)

    options forwarded to every HTTP::Session (headers, timeout, ssl, etc.)

Raises:

  • (ArgumentError)


37
38
39
40
41
42
43
44
45
46
47
48
# File 'lib/http_connection_pool/pool.rb', line 37

def initialize(origin:, size: DEFAULT_SIZE, timeout: DEFAULT_TIMEOUT, **options)
  @origin  = origin
  @size    = Integer(size)
  @timeout = Float(timeout)
  @options = deep_freeze(options)

  raise ArgumentError, 'size must be >= 1' unless @size >= 1
  raise ArgumentError, 'timeout must be > 0' unless @timeout.positive?

  @closed = Concurrent::AtomicBoolean.new(false)
  @pool   = ::ConnectionPool.new(size: @size, timeout: @timeout) { build_connection }
end

Instance Attribute Details

#originObject (readonly)

Returns the value of attribute origin.



50
51
52
# File 'lib/http_connection_pool/pool.rb', line 50

def origin
  @origin
end

#sizeObject (readonly)

Returns the value of attribute size.



50
51
52
# File 'lib/http_connection_pool/pool.rb', line 50

def size
  @size
end

Instance Method Details

#closeObject

Immediately close every connection and mark the pool as closed. Any subsequent call to #with will raise ClosedError.



73
74
75
76
77
78
79
80
81
82
# File 'lib/http_connection_pool/pool.rb', line 73

def close
  return unless @closed.make_true

  @pool.shutdown do |conn|
    conn&.close
  rescue StandardError
    # Closing a stale connection should not mask the shutdown.
    nil
  end
end

#closed?Boolean

Returns:

  • (Boolean)


84
85
86
# File 'lib/http_connection_pool/pool.rb', line 84

def closed?
  @closed.true?
end

#inspectObject Also known as: to_s

Redacted inspect. The default Ruby #inspect would dump @options verbatim, exposing any Authorization header / auth token / SSL material in logs, backtraces, and error-reporting payloads. We list only the option keys (never their values) plus the non-sensitive pool state.



103
104
105
106
107
108
# File 'lib/http_connection_pool/pool.rb', line 103

def inspect
  keys = @options.keys
  option_keys = keys.empty? ? 'none' : keys.join(', ')
  "#<#{self.class.name} origin=#{@origin.inspect} size=#{@size} " \
    "timeout=#{@timeout} closed=#{closed?} options=[#{option_keys}]>"
end

#pretty_print(pp) ⇒ Object

Belt-and-suspenders for pretty-printers (pp / awesome_print), which call #pretty_print rather than #inspect and would otherwise reach @options.



113
114
115
# File 'lib/http_connection_pool/pool.rb', line 113

def pretty_print(pp)
  pp.text(inspect)
end

#statsObject



88
89
90
91
92
93
94
95
96
97
# File 'lib/http_connection_pool/pool.rb', line 88

def stats
  available = @pool.available
  {
    origin: @origin,
    size: @size,
    checked_out: @size - available,
    idle: available,
    closed: closed?
  }
end

#with {|conn| ... } ⇒ Object

Yields a live HTTP::Session scoped to @origin, returning it to the pool when done.

Yield Parameters:

  • conn (HTTP::Session)

Returns:

  • (Object)

    the value returned by the block

Raises:

  • (ClosedError)

    if the pool has been shut down

  • (TimeoutError)

    if no connection is available within the configured timeout



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/http_connection_pool/pool.rb', line 58

def with(&)
  raise ClosedError, "pool for #{@origin} is closed" if @closed.true?

  @pool.with(&)
rescue ::ConnectionPool::TimeoutError => e
  raise TimeoutError, "no connection available for #{@origin} within #{@timeout}s (#{e.message})"
rescue ::ConnectionPool::PoolShuttingDownError
  # Another thread closed the pool between our @closed check and checkout.
  # Surface it as our own ClosedError rather than leaking the backing
  # library's exception type.
  raise ClosedError, "pool for #{@origin} is closed"
end