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 -> readyEverything 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
@type failover?() :: boolean()
@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() }