1
votes

I'm using the elixir-socket library as a way to connect to my backend application to an external websocket. I need this process to be managed (restart if something fails, backoff exponentially if it can't connect etc).

Currently, I've created a managing GenServer process that spawns a looping socket after a given amount of time (simplified below). I have a supervisor managing the SocketManager (and therefore the linked Socket) process:

socket_manager.ex

defmodule MyApp.SocketManager do
  def init(_) do
    Process.flag(:trap_exit, true)
    state = %{socket: nil}
    {:ok, state, {:continue, :init}}
  end

  def handle_continue(:init, state) do
    Task.start_link(fn ->
      Socket.connect!()
    end)
    {:noreply, state}
  end
end

socket.ex

defmodule MyApp.Socket do
  def connect! do
    socket = Socket.Web.connect!("xx.xx.com", secure: true, path: "/api/xxx")
    SocketManager.socket_connected(socket) # save the socket in the SocketManager state
    listen(socket)
  end

  defp listen(socket) do
    case socket |> Socket.Web.recv!() do
      {:text, data} ->
        # handle message
      {:close, :abnormal, _} ->
        Process.exit(self(), :kill)
      {:pong, _} ->
        nil
    end
    listen(socket)
  end
end

The above works great, however I'm not sure that this is the best way to structure this. From what I understand, Task should only be for tasks that have a defined lifespan, not for perpetual process. Also, when running mix dialyzer I get the following ouptut (referring to the Task.spawn_link line in SocketManager):

lib/myapp/socket_manager.ex:40:no_return
The created fun has no local return.

Can anyone help me with suggestions on how else to structure this and how I might be able to satisfy Dialyzer?

Thanks!

1
Sidenote: to satisfy dializer one writes typespecs in the first place.Aleksei Matiushkin
AFAICT, the proper way to go here would be a DynamicSupervisor.Aleksei Matiushkin
Thanks! I'll give DynamicSupervisor a tryAndre Ogle

1 Answers

0
votes

If anyone else is interested, this is what I ended up with. I think it's is a slightly better structure, although there are probably better/more idiomatic ways. It uses a DynamicSupervisor to supervise the socket process. It also no longer attempts to connect when the process

socket_manager.ex

defmodule MyApp.SocketManager do
  def start_link(_) do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def connect do
    GenServer.cast(__MODULE__, {:connect})
  end

  def handle_cast({:connect}, state) do
    spec = %{
      id: LiveSocket,
      start: {MyApp.Socket, :connect, []},
      type: :worker
    }
    {:ok, pid} = DynamicSupervisor.start_child(MyApp.DynamicSupervisor, spec)
    Process.link(pid)
    {:noreply, state}
  end
end