Packages
gnat
1.15.1
1.16.0
1.15.2
1.15.1
1.15.0
1.14.1
1.14.0
1.14.0-rc1
1.14.0-rc0
1.13.1
1.13.0
1.12.1
1.12.0
1.11.1
1.11.0
1.10.2
1.10.2-rc1
1.10.2-rc0
1.10.1
1.10.0
1.9.1
1.9.0
1.8.5
1.8.4
1.8.3
1.8.2
1.8.1
1.7.1
1.7.0
1.6.0
1.5.2
1.5.1
1.5.0
1.4.0
1.3.0
1.3.0-rc0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.1
1.1.0
1.0.1
1.0.0
0.7.0
0.6.1
0.6.0
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.0
0.1.3
0.1.2
0.1.1
0.1.0
A nats client in pure elixir. Resilience, Performance, Ease-of-Use.
Current section
Files
Jump to
Current section
Files
lib/gnat/connection_supervisor.ex
defmodule Gnat.ConnectionSupervisor do
use GenServer
require Logger
@moduledoc """
A process that can supervise a named connection for you
If you would like to supervise a Gnat connection and have it automatically re-connect in case of failure you can use this module in your supervision tree.
It takes a map with the following data:
```
gnat_supervisor_settings = %{
name: :gnat, # (required) the registered named you want to give the Gnat connection
backoff_period: 4_000, # number of milliseconds to wait between consecutive reconnect attempts (default: 2_000)
connection_settings: [
%{host: '10.0.0.100', port: 4222},
%{host: '10.0.0.101', port: 4222},
]
}
```
The connection settings can specify all of the same values that you pass to `Gnat.start_link/1`. Each time a connection is attempted we will use one of the provided connection settings to open the connection. This is a simplistic way of load balancing your connections across a cluster of nats nodes and allowing failover to other nodes in the cluster if one goes down.
To use this in your supervision tree add an entry like this:
```
import Supervisor.Spec
worker(Gnat.ConnectionSupervisor, [gnat_supervisor_settings, [name: :my_connection_supervisor]])
```
The second argument is used as GenServer options so you can give the supervisor a registered name as well if you like. Now in the rest of your code you can call things like:
```
:ok = Gnat.pub(:gnat, "subject", "message")
```
And it will use your supervised connection. If the connection is down when you call that function (or dies during that function) it will raise an error.
"""
@spec start_link(map(), keyword()) :: GenServer.on_start()
def start_link(settings, options \\ []) do
GenServer.start_link(__MODULE__, settings, options)
end
@impl GenServer
def init(options) do
state = %{
backoff_period: Map.get(options, :backoff_period, 2000),
connection_settings: Map.fetch!(options, :connection_settings),
name: Map.fetch!(options, :name),
gnat: nil
}
Process.flag(:trap_exit, true)
send(self(), :attempt_connection)
{:ok, state}
end
@impl GenServer
def handle_info(:attempt_connection, state) do
connection_config = random_connection_config(state)
Logger.debug("connecting to #{inspect(connection_config)}")
case Gnat.start_link(connection_config, name: state.name) do
{:ok, gnat} ->
{:noreply, %{state | gnat: gnat}}
{:error, err} ->
Logger.error("failed to connect #{inspect(err)}")
Process.send_after(self(), :attempt_connection, state.backoff_period)
{:noreply, %{state | gnat: nil}}
end
end
# in OTP 25 and below, we will get back an EXIT message in addition to receiving the {:error, reason}
# tuple on from the start_link call above. So if we get an exit message when there is no connection tracked
# it means will have already scheduled a new attempt_connection
def handle_info({:EXIT, _pid, _reason}, %{gnat: nil} = state) do
{:noreply, state}
end
def handle_info({:EXIT, _pid, reason}, state) do
Logger.error("connection failed #{inspect(reason)}")
send(self(), :attempt_connection)
{:noreply, state}
end
def handle_info(msg, state) do
Logger.error("#{__MODULE__} received unexpected message #{inspect(msg)}")
{:noreply, state}
end
defp random_connection_config(%{connection_settings: connection_settings}) do
connection_settings |> Enum.random()
end
end