One AMQP connection + confirm-mode channel per {instance, url, exchange}.
Declares the configured exchange whenever it opens the channel (idempotent) —
never a queue; queue ownership belongs to consumers, not this sink.
Every publish is mandatory and is answered on the broker's confirm of that
publish, so publish/4 answers :ok only when at least one queue bound to
the exchange accepted the message. A message the exchange routes to no queue
is {:error, {:unroutable, routing_key}}, one the broker refuses (a queue's
reject-publish overflow) is {:error, :nacked}, and no confirm within
:confirm_timeout_ms milliseconds is {:error, :confirm_timeout}.
Confirms are asynchronous
A publish is sent and its caller parked under the publish's sequence number;
the confirm, whenever it arrives, answers it. Many publishes are in flight on
the channel at once (up to :max_inflight, default 256; past that a publish
is {:error, :busy}), so one slow confirm never serializes the others.
Every call carries its caller's deadline. A publish still waiting in this
process's mailbox when its caller gave up (publish/4 waits 15 s) is
answered {:error, :expired} and never sent: an abandoned call is not
published behind the caller's back.
The channel is monitored. When the broker closes it (a publish to a deleted
exchange is a 404) every publish waiting on it answers
{:error, {:channel_closed, reason}} and the channel is reopened at once on
the same connection, re-declaring the exchange.
The connection dials after start_link/1 returns, and connection loss is
not treated as a crash — this GenServer stays up and retries on a timer,
replying {:error, :not_connected} to publishes in the meantime (and to any
that were waiting when it dropped). Every one of these errors flows into the
source's Ankusa.RetryPolicy exactly like any other sink failure, so there
is no separate reconnect policy to get wrong.
The AMQP connection belongs to amqp_client's own supervisor, not to this
process, so stopping this process (its supervisor's shutdown, or the idle
sweep of Ankusa.Sink.Reaper) closes it explicitly and answers every
waiting publish {:error, :not_connected}.
Summary
Functions
@spec call_timeout_ms() :: pos_integer()
The longest publish/4 waits, in milliseconds.
Returns a specification to start this module under a supervisor.
See Supervisor.
@spec default_url() :: String.t()
The AMQP URL this connection uses when :url is not configured.
@spec publish(GenServer.server(), String.t(), binary(), keyword()) :: :ok | {:error, term()}