Arangox.Connection (Arangox v0.8.0)

Copy Markdown View Source

DBConnection implementation for Arangox.

The connect pipeline

connect/1 walks the configured endpoints in order and runs one linear pipeline per endpoint:

resolve options -> open (do_connect) -> resolve_auth
  -> check_availability -> discover_version -> ready

Everything after do_connect owns an open socket, so every exit from those stages other than a successful return closes that socket first. Failing to do so leaks a socket per attempt, not per process: DBConnection retries connect/1 in the same process under backoff, so the leaked sockets pile up in a live process instead of dying with it.

check_availability has a fourth outcome besides ok/next/error: a 503 naming the current leader in x-arango-endpoint re-enters the walk at that leader, once the redirect admission policy has admitted it. See the comments above redirect/3 for the policy and Arangox.start_link/1 for the user-facing rules and the :endpoint_mapper trust boundary.

No stage raises. Endpoint parsing goes through Arangox.Endpoint.parse/1 and response bodies are decoded with a non-raising decoder, because a raise here escapes the DBConnection callback and turns an orderly backoff into a supervisor crash loop.

Credentials never reach state, an error or a log

new/4 stores Arangox.Endpoint.redact/1 of the configured endpoint, not the configured endpoint. That single substitution covers three exits at once: the :endpoint field of every Arangox.Error built here, Exception.message/1 (which prepends [endpoint]), and the failed to connect line DBConnection logs on every backoff cycle.

Redacting only at render would have left inspect(state) leaking, and DBConnection's own :show_sensitive_data_on_connection_error does not help: it sanitizes only exceptions that escape connect/1, and this module never lets one escape — it returns {:error, exception}, which DBConnection logs unsanitized. There is no opt-out, so the redaction is unconditional and there is no option to turn it off.

:auth and the resolved authorization header still hold the credential in state, because the request path needs them; the Inspect implementation at the bottom of this file redacts both.

Cost of the walk

The walk is per connect attempt, and a connect attempt is retried on the usual :backoff_min/:backoff_max schedule for as long as the pool lives. With N endpoints that are all down, every backoff cycle of every pool process costs N connection attempts, N :failover_callback invocations and one failed to connect log line. A pool of 10 against 3 dead endpoints at the default one-second minimum backoff is therefore ~30 connection attempts and ~30 callback invocations per second. Keep :failover_callback cheap and non-blocking, and raise :backoff_min if the log volume matters.

Summary

Types

failover?()

@type failover?() :: boolean()

t()

@type t() :: %Arangox.Connection{
  auth: Arangox.Auth.t(),
  client: module(),
  content_type: :json | :velocypack,
  cursors: map(),
  database: binary(),
  disconnect_on_error_codes: [integer()],
  endpoint: Arangox.endpoint(),
  failover?: boolean(),
  headers: Arangox.headers(),
  json_library: module(),
  max_body_size: pos_integer(),
  parsed_endpoint: Arangox.Endpoint.t() | nil,
  read_only?: boolean(),
  request_timeout: pos_integer() | nil,
  server_version: Version.t() | nil,
  socket: any(),
  trx_id: binary() | nil,
  vst_maxsize: pos_integer()
}