Connection
A behaviour module for implementing connection processes.
The Connection
behaviour is a superset of the GenServer
behaviour. The
additional return values and callbacks are designed to aid building a
connection process that can handle a peer being (temporarily) unavailable.
An example Connection
process:
defmodule TCPConnection do
use Connection
def start_link(host, port, opts, timeout \\ 5000) do
Connection.start_link(__MODULE__, {host, port, opts, timeout})
end
def send(conn, data), do: Connection.call(conn, {:send, data})
def recv(conn, bytes, timeout \\ 3000) do
Connection.call(conn, {:recv, bytes, timeout})
end
def close(conn), do: Connection.call(conn, :close)
def init({host, port, opts, timeout}) do
s = %{host: host, port: port, opts: opts, timeout: timeout, sock: nil}
{:connect, :init, s}
end
def connect(_, %{sock: nil, host: host, port: port, opts: opts,
timeout: timeout} = s) do
case :gen_tcp.connect(host, port, [active: false] ++ opts, timeout) do
{:ok, sock} ->
{:ok, %{s | sock: sock}}
{:error, _} ->
{:backoff, 1000, s}
end
end
def disconnect(info, %{sock: sock} = s) do
:ok = :gen_tcp.close(sock)
case info do
{:close, from} ->
Connection.reply(from, :ok)
{:error, :closed} ->
:error_logger.format("Connection closed~n", [])
{:error, reason} ->
reason = :inet.format_error(reason)
:error_logger.format("Connection error: ~s~n", [reason])
end
{:connect, :reconnect, %{s | sock: nil}}
end
def handle_call(_, _, %{sock: nil} = s) do
{:reply, {:error, :closed}, s}
end
def handle_call({:send, data}, _, %{sock: sock} = s) do
case :gen_tcp.send(sock, data) do
:ok ->
{:reply, :ok, s}
{:error, _} = error ->
{:disconnect, error, error, s}
end
end
def handle_call({:recv, bytes, timeout}, _, %{sock: sock} = s) do
case :gen_tcp.recv(sock, bytes, timeout) do
{:ok, _} = ok ->
{:reply, ok, s}
{:error, :timeout} = timeout ->
{:reply, timeout, s}
{:error, _} = error ->
{:disconnect, error, error, s}
end
end
def handle_call(:close, from, s) do
{:disconnect, {:close, from}, s}
end
end
The example above follows a common pattern. Try to connect immediately, if that fails backoff and retry after a delay. If a retry fails make another attempt after another delay. If the process disconnects a reconnection attempt is made immediately, if that fails backoff begins.
Importantly when backing off requests will still be received by the process,
which will need to be handled. In the above example the process replies with
{:error, :closed}
when it is disconnected.
Summary↑
call(conn, req) | Sends a synchronous call to the |
call(conn, req, timeout) | Sends a synchronous request to the |
cast(conn, req) | Sends a asynchronous request to the |
reply(from, response) | Sends a reply to a request sent by |
start(mod, args, opts \\ []) | Starts a |
start_link(mod, args, opts \\ []) | Starts a |
Functions
Sends a synchronous call to the Connection
process and waits for a reply.
See GenServer.call/2
for more information.
Sends a synchronous request to the Connection
process and waits for a reply.
See GenServer.call/3
for more information.
Sends a asynchronous request to the Connection
process.
See GenServer.cast/2
for more information.
Sends a reply to a request sent by call/3
.
See GenServer.reply/2
for more information.
Specs:
- start(module, any, GenServer.options) :: GenServer.on_start
Starts a Connection
process without links (outside of a supervision tree).
See start_link/3
for more information.
Specs:
- start_link(module, any, GenServer.options) :: GenServer.on_start
Starts a Connection
process linked to the current process.
This function is used to start a Connection
process in a supervision tree.
The process will be started by calling init/1
in the callback module with
the given argument.
This function will return after init/1
has returned in the spawned process.
The return values are controlled by the init/1
callback.
See GenServer.start_link/3
for more information.