Using Elixir Dynamic Supervisors
转自: https://blog.smartlogic.io/elixir-dynamic-supervisors/
I have been working on my side project Grapevine, a community site for text based multiplayer games (MUDs.) The existing games primarily have a telnet interface for playing. This generally involves using a local client or hoping the game provides a web client for you. So I've been working on a general purpose client that any game can use. You can see it in action for my game MidMUD.
This involves a new web client that uses Phoenix Channels to spin up a backing :gen_tcp
process. This process is started inside of a DynamicSupervisor, a new feature introduced in Elixir 1.6. We place the process inside of the supervisor to keep the process alive across page reloads. Lets see how it works!
Basic Flow
When a new web client starts they join a channel named play:client
. The channel ensures that the game they are trying to connect to exists and starts the backing process.
{:ok, pid} = WebClient.connect(socket.assigns.user,
game_id: socket.assigns.game.id,
host: connection.host,
port: connection.port,
channel_pid: socket.channel_pid
)
This Client
process starts itself inside of a DynamicSupervisor as a transient
process and saves the calling channel PID in order to forward any received data to the front end. This uses the DynamicSupervisor.start_child/2
function.
def start_client(callback_module, opts) do
spec = {Client, [module: callback_module] ++ opts}
DynamicSupervisor.start_child(__MODULE__, spec)
end
Once the Client
process is alive it parses the telnet stream and forwards any text directly to the web front end.
Handling Page Reloads
The Client
process is globally registered with the user's ID and the game's ID as part of the name, {:webclient, {1, 1}}
. With this, we can check :global
to see if the process already exists before starting a new process. If the process exists we overtake the Client
process with our new channel.
def connect(user, opts) do
case :global.whereis_name(pid(user, opts)) do
:undefined ->
ClientSupervisor.start_client(__MODULE__, opts ++ [name: {:global, pid(user, opts)}])
pid ->
set_channel(pid, opts[:channel_pid])
{:ok, pid}
end
end
set_channel/2
sends a message to the process which sets the internal channel_pid
state for forwarding text to the web client.
Conclusion
With this setup I'm able to have a place to supervise Client
processes and allow for session "stickiness" on page reloads. The DynamicSupervisor will delete the child_spec
of the Client
process after it terminates with a :normal
reason. Otherwise the process will be restarted allowing for minor hiccups in the network.
All of this code is open source on GitHub, check out the Grapevine repo. I also worked on this during my Elixir live coding stream. I'll be back doing more live coding on that channel every Monday from 12PM-1PM Eastern.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)