Module: AtlasRb::Transport

Defined in:
lib/atlas_rb/transport.rb

Overview

Process-wide cache of Faraday connections, so Atlas calls reuse sockets instead of paying a TCP (and, under TLS, a full TLS) handshake per request.

Why a cache is needed at all

Keep-alive is what saves the handshake, and keep-alive needs two things that a per-request Faraday.new cannot give it. The pool lives on the adapter instance, and Faraday builds a new adapter for every connection — so a connection built per request gets a fresh, empty pool and reuses nothing. The socket has to outlive the request, which means the connection holding it has to as well.

The cache is keyed on connection shape (:json, :multipart, :system) and base URL, because those are the only things that vary at build time now — per-request state (the signed assertion, the Idempotency-Key, the query params) rides on Proxy instead.

Why not a connection per thread

A thread-local connection is the obvious cheap version and it is wrong for this consumer: Cerberus fans out its page reads on short-lived threads, so a thread-local pool dies with the thread that built it and the fan-out — the calls that most want reuse — reuses nothing. net-http-persistent keeps a shared ConnectionPool and uses Thread.current only to track re-entrant checkouts, so a socket checked in by a dying thread goes back to the shared stack.

Thread safety

A Faraday connection is safe for concurrent requests as long as nothing mutates it after it is built. Everything here respects that: the cached connection is built once under MUTEX (with its middleware stack forced so two threads cannot race to build two adapters, and therefore two pools), and per-request headers and params are set on the request, never assigned onto the shared connection.

Defined Under Namespace

Classes: Proxy

Constant Summary collapse

DEFAULT_POOL_SIZE =

Sockets kept per host. Sized for the consumer's shape rather than left at the net-http-persistent default (256, or a quarter of the open-file limit): Cerberus draws up to four concurrent reads per Puma thread, so the useful number is that fan-out times the thread count, plus headroom.

16

Class Method Summary collapse

Class Method Details

.configure_persistent(http) ⇒ void

This method returns an undefined value.

Apply the pool settings a host has configured to a Net::HTTP::Persistent. Called by the adapter's config block on every request, so it must stay assignment-only and cheap.

max_retries is restored to 1 here because the adapter zeroes it, and zero is the wrong default for a pooled socket: a server that closed an idle connection produces an error on the next write, and one retry is what turns that into a reconnect instead of a caller-visible failure. Net::HTTP gates its retry on IDEMPOTENT_METHODS_, so the retry-safe creates (POST /works, /file_sets, /files) are never replayed.

Parameters:

  • http (Net::HTTP::Persistent)


159
160
161
162
# File 'lib/atlas_rb/transport.rb', line 159

def configure_persistent(http)
  http.max_retries  = 1
  http.max_requests = AtlasRb.config.connection_max_requests
end

.connection_for(key) ⇒ Faraday::Connection

Fetch the cached connection for key, building it from the block on first use.

Parameters:

  • key (Array)

    connection shape and base URL.

Yield Returns:

  • (Faraday::Connection)

    a freshly built connection.

Returns:

  • (Faraday::Connection)

    the shared connection for key.



132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/atlas_rb/transport.rb', line 132

def connection_for(key)
  MUTEX.synchronize do
    connections[key] ||= begin
      conn = yield
      # Force the middleware stack (and with it the adapter, and with it
      # the pool) while still holding the lock. Faraday memoizes the app
      # lazily on first request, and two threads racing that memoization
      # would each build an adapter — two pools, no reuse.
      conn.builder.app
      conn
    end
  end
end

.parsed_key(pem) ⇒ OpenSSL::PKey::PKey

Parse a PEM into an OpenSSL::PKey, reusing the last parse.

Parsing the key costs roughly five times the ES256 signature it exists to produce, and the signing key is configured as a callable returning a PEM, so it was being reparsed on every request. One entry is enough — the cache is a rotation check, not a store, so a rotated key parses once and the old one is dropped rather than accumulating.

Parameters:

  • pem (String)

    the PEM-encoded key.

Returns:

  • (OpenSSL::PKey::PKey)


181
182
183
184
185
186
187
188
189
# File 'lib/atlas_rb/transport.rb', line 181

def parsed_key(pem)
  KEY_MUTEX.synchronize do
    if @parsed_pem != pem
      @parsed_pem = pem
      @parsed_key = OpenSSL::PKey.read(pem)
    end
    @parsed_key
  end
end

.pool_sizeInteger

Pool size for a newly built adapter.

Returns:

  • (Integer)


167
168
169
# File 'lib/atlas_rb/transport.rb', line 167

def pool_size
  AtlasRb.config.connection_pool_size || DEFAULT_POOL_SIZE
end

.reset_connections!void

This method returns an undefined value.

Close every pooled socket and drop the cached connections.

Pooled sockets outliving a test example are a new source of cross-example coupling, so a suite that boots and tears down a real server (Atlas's :atlas_rb_server layer) should call this in its teardown. Shutting a net-http-persistent pool down makes it refuse later checkouts, which is why the cached connections are dropped in the same breath — the next call rebuilds both.



201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/atlas_rb/transport.rb', line 201

def reset_connections!
  MUTEX.synchronize do
    connections.each_value do |conn|
      conn.close
    rescue StandardError
      # A pool already shut down, or a socket already gone, has nothing
      # left to release. The connection is being discarded either way.
      nil
    end
    connections.clear
  end
end