I’m really not sure where the mental block is but I cannot work out Supervisors. I’ve revisited the guides on elixir-lang.org but I can’t wrap my head about it.
Trying to use Ranch 1.8 to add TCP & TLS listeners to a Phoenix project, my progress has been:
- Adding a protocol handler:
defmodule MyApp.TCPServer do
alias MyApp.Message
@behaviour :ranch_protocol
def start_link(ref, socket, transport, mod: mod) do
pid = :proc_lib.spawn_link(__MODULE__, :init, [ref, socket, transport, mod])
{:ok, pid}
end
def init(ref, _, transport, mod) do
{:ok, socket} = :ranch.handshake(ref)
:ok = transport.setopts(socket, [{:active, true}])
:gen_server.enter_loop(__MODULE__, [], %{
ref: ref,
socket: socket,
transport: transport,
mod: mod
})
end
def handle_info({:tcp, socket, data}, %{socket: socket, transport: transport, mod: mod} = state) do
{:ok, %{} = msg} = Message.decode(data)
case mod.handle_message(msg, state) do
{:ok, reply, new_state} ->
{:ok, bin} = Message.encode(reply)
transport.send(socket, bin)
{:noreply, new_state}
:noreply ->
{:noreply, state}
{:error, e} ->
transport.close(socket)
{:stop, :normal, state}
end
end
def handle_info({:tcp_closed, socket}, %{socket: socket, transport: transport} = state) do
transport.close(socket)
{:stop, :normal, state}
end
def handle_info({:tcp_error, socket}, %{socket: socket} = state) do
{:stop, :normal, state}
end
end
- A TCP Listener:
defmodule MyApp.TCPListener do
def start_link(), do: :ranch.start_listener(make_ref(), :ranch_tcp, [port: 4040], MyApp.TCPServer, [])
def start(_, _) do
{:ok, _} = :ranch.start_listener(MyApp.TCP, 5, :ranch_tcp, [port: 4040], MyApp.TCPServer, [])
end
end
- And a Supervisor for the Ranch listener:
defmodule MyApp.SocketSupervisor do
use Supervisor
def start_link(_args) do
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
def init([]) do
children = [
worker(MyApp.TCPListener, [])
]
opts = [strategy: :one_for_one, max_restarts: 3]
supervise(children, opts)
end
end
- Include
MyApp.SocketSupervisor
in the main Supervisor defined by Phoenix generators:
defmodule MyApp.Application do
use Application
@impl true
def start(_type, _args) do
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
[
MyApp.Repo,
MyAppWeb.Telemetry,
{Phoenix.PubSub, name: MyApp.PubSub},
MyAppWeb.Endpoint,
MyApp.SocketSupervisor # <- This line.
]
|> Supervisor.start_link(opts)
end
@impl true
def config_change(changed, _new, removed) do
MyAppWeb.Endpoint.config_change(changed, removed)
:ok
end
end
Originally I had MyApp.TCPListener.start_link/1
defined following examples instead of start_link/0
& start/2
but that complained quite a bit. But even after correcting that I’m getting this error on start:
** (Mix) Could not start application my_app: MyApp.Application.start(:normal, []) returned an error: shutdown: failed to start child: MyApp.SocketSupervisor
** (EXIT) shutdown: failed to start child: MyApp.TCPListener
** (EXIT) {{:shutdown, {:failed_to_start_child, :ranch_acceptors_sup, {:badarg, [{:lists, :keymember, [:backlog, 1, nil], [error_info: %{module: :erl_stdlib_errors}]}, {:ranch, :set_option_default, 3, [file: '/tmp/my_app/deps/ranch/src/ranch.erl', line: 468]}, {:ranch_tcp, :listen, 1, [file: '/tmp/my_app/deps/ranch/src/ranch_tcp.erl', line: 84]}, {:ranch_acceptors_sup, :init, 1, [file: '/tmp/my_app/deps/ranch/src/ranch_acceptors_sup.erl', line: 39]}, {:supervisor, :init, 1, [file: 'supervisor.erl', line: 330]}, {:gen_server, :init_it, 2, [file: 'gen_server.erl', line: 423]}, {:gen_server, :init_it, 6, [file: 'gen_server.erl', line: 390]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 226]}]}}}, {:child, :undefined, {:ranch_listener_sup, #Reference<0.3723464176.968359939.121336>}, {:ranch_listener_sup, :start_link, [#Reference<0.3723464176.968359939.121336>, :ranch_tcp, %{socket_opts: nil}, MyApp.TCPServer, []]}, :permanent, false, :infinity, :supervisor, [:ranch_listener_sup]}}
It’s obvious I’ve no idea what I’m doing but would anyone know what it is I’m doing wrong?
Thanks.