class Gems::ConnectionPool
Keeps the connections a {Connection} is not using, so that a request can be sent on one that is already open
One connection is kept for each host, so that requests made at the same time are sent on connections of their own rather than waiting for one another, and the connections they opened are closed when they are given back. A connection opened with settings that have since changed is closed rather than reused.
A process forked from one that kept a connection open shares the socket of that connection with it, so the connection is kept for the process that opened it alone: a forked process opens one of its own, rather than reading the responses to the requests of the other process, and leaves the one it inherited alone, rather than closing it under the process still using it.
@api private
Public Class Methods
Initialize a new ConnectionPool
@api private @return [ConnectionPool] a new instance @example Create a connection pool
pool = Gems::ConnectionPool.new
# File lib/gems/connection_pool.rb, line 28 def initialize @connections = {} @mutex = Mutex.new end
Public Instance Methods
Keep the connection a request was sent on, or close it
@api private @param request [Net::HTTPRequest] the request @param http_client [Net::HTTP] the connection it was sent on @param settings [Object] the settings the connection was opened with @param keep_alive_timeout [Numeric] the seconds an idle connection is kept open, or zero to close it @return [void] @example Give back the connection a request was sent on
pool.checkin(request:, http_client:, settings:, keep_alive_timeout: 2)
# File lib/gems/connection_pool.rb, line 61 def checkin(request:, http_client:, settings:, keep_alive_timeout:) return discard(http_client) unless keep_alive?(request, keep_alive_timeout) store(pool_key(request), http_client, settings) end
The connection to send a request on
A connection kept open for the host of the request is used when the request can be sent on one, and one is opened with the block otherwise.
@api private @param request [Net::HTTPRequest] the request @param settings [Object] the settings a connection must have been opened with to be reused @param keep_alive_timeout [Numeric] the seconds an idle connection is kept open, or zero to open one each time @yield the connection to open when none is kept for the host of the request @return [Net::HTTP] the connection, which is open @example Take the connection to send a request on
pool.checkout(request:, settings:, keep_alive_timeout: 2) { Net::HTTP.new(uri.host, uri.port) }
# File lib/gems/connection_pool.rb, line 46 def checkout(request:, settings:, keep_alive_timeout:) http_client = take(pool_key(request), settings) if keep_alive?(request, keep_alive_timeout) http_client || yield.tap(&:start) end
Close every connection the pool keeps
The keys are read under the lock, as every other read and write of the connections is, since a request giving its connection back at that moment writes to them from another thread. The connections are then taken one at a time, so that a connection given back while the pool is closing is either closed with the rest or kept for the next request, rather than dropped without being closed. The connections opened by the process this one was forked from are forgotten rather than closed, since that process may still be using them.
@api private @return [ConnectionPool] the pool @example Close the connections of a pool
pool.close
# File lib/gems/connection_pool.rb, line 132 def close keys = @mutex.synchronize { @connections.keys } keys.each { |key| discard(delete(key)) } self end
Close a connection
@api private @param http_client [Net::HTTP, nil] the connection, or nil when none was opened @return [void] @example Close the connection a request failed on
pool.discard(http_client)
# File lib/gems/connection_pool.rb, line 74 def discard(http_client) http_client.finish if http_client&.started? end
Keep a connection for a key
A connection is kept only when none is kept for the key already, so that the pool holds one connection per host; a connection it cannot keep is closed. One kept by the process this one was forked from does not count, and is replaced.
@api private @param key [Object] the key to keep the connection under @param http_client [Net::HTTP] the connection @param settings [Object] the settings the connection was opened with @return [Boolean] whether the connection was kept @example Keep a connection for a host
pool.store(["https", "rubygems.org", 443], http_client, settings)
# File lib/gems/connection_pool.rb, line 112 def store(key, http_client, settings) stored = @mutex.synchronize do @connections[key] = [http_client, settings, Process.pid] unless opened_here?(@connections.dig(key, 2)) end discard(http_client) if stored.nil? !stored.nil? end
Take the connection kept for a key
The connection is removed from the pool, so that it is used by one request at a time, and is given back with {#store}. A connection opened with other settings is closed instead of returned, and one opened by the process this one was forked from is left to that process.
@api private @param key [Object] the key the connection is kept under @param settings [Object] the settings a connection must have been opened with to be reused @return [Net::HTTP, nil] the connection, or nil when none is kept for the key @example Take the connection kept for a host
pool.take(["https", "rubygems.org", 443], settings)
# File lib/gems/connection_pool.rb, line 90 def take(key, settings) http_client, kept_settings, pid = @mutex.synchronize { @connections.delete(key) } return unless opened_here?(pid) return http_client if settings.eql?(kept_settings) discard(http_client) nil end