使用侦听过程构建 Elixir GenServer 的正确方法
Correct way to structure Elixir GenServer with listening process
我正在使用 elixir-socket 库将我的后端应用程序连接到外部 websocket。我需要管理此过程(如果出现故障则重新启动,如果无法连接则呈指数退避等)。
目前,我已经创建了一个管理 GenServer 进程,它在给定的时间后产生一个循环套接字(下面进行了简化)。我有一位主管负责管理 SocketManager
(以及链接的 Socket
)流程:
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
以上内容效果很好,但我不确定这是构建此内容的最佳方式。据我了解,Task
只适用于具有明确生命周期的任务,而不适用于永久过程。此外,当 运行 mix dialyzer
我得到以下输出(参考 SocketManager
中的 Task.spawn_link
行):
lib/myapp/socket_manager.ex:40:no_return
The created fun has no local return.
任何人都可以就如何构建它以及我如何能够满足 Dialyzer 提出建议吗?
谢谢!
如果其他人有兴趣,这就是我最终得到的。我认为这是一个稍微好一点的结构,尽管可能有 better/more 惯用的方法。它使用 DynamicSupervisor 来监督套接字进程。当进程
时,它也不再尝试连接
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
我正在使用 elixir-socket 库将我的后端应用程序连接到外部 websocket。我需要管理此过程(如果出现故障则重新启动,如果无法连接则呈指数退避等)。
目前,我已经创建了一个管理 GenServer 进程,它在给定的时间后产生一个循环套接字(下面进行了简化)。我有一位主管负责管理 SocketManager
(以及链接的 Socket
)流程:
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
以上内容效果很好,但我不确定这是构建此内容的最佳方式。据我了解,Task
只适用于具有明确生命周期的任务,而不适用于永久过程。此外,当 运行 mix dialyzer
我得到以下输出(参考 SocketManager
中的 Task.spawn_link
行):
lib/myapp/socket_manager.ex:40:no_return
The created fun has no local return.
任何人都可以就如何构建它以及我如何能够满足 Dialyzer 提出建议吗?
谢谢!
如果其他人有兴趣,这就是我最终得到的。我认为这是一个稍微好一点的结构,尽管可能有 better/more 惯用的方法。它使用 DynamicSupervisor 来监督套接字进程。当进程
时,它也不再尝试连接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