今天咱们将为德州扑克游戏添加故障恢复能力。app
OTP为咱们准备好了构建容错程序所须要的工具。咱们只须要定义正确的behavior 行为。函数
有了Supervisor,咱们就只须要关心当进程崩溃时如何反应。首先,咱们使用顶层的Supervisor——Application:工具
defmodule GenPoker do use Application def start(_type, _args) do import Supervisor.Spec children = [ worker(Poker.Bank, []) ] opts = [strategy: :one_for_one, name: GenPoker.Supervisor] Supervisor.start_link(children, opts) end end
在mix.exs中注册咱们的应用模块:测试
def application do [mod: {GenPoker, []}] end
当工做中的进程崩溃后,会新建一个新的进程,打开 iex -S mix 测试一下:atom
iex(1)> Process.whereis(Poker.Bank) #PID<0.93.0> iex(2)> Process.whereis(Poker.Bank) |> Process.exit(:kill) true iex(3)> Process.whereis(Poker.Bank) #PID<0.97.0>
咱们能够把牌桌和牌局进程放在同一个Supervisor下:rest
defmodule Poker.Table.Supervisor do use Supervisor def start_link(table_name, num_players) do Supervisor.start_link(__MODULE__, [table_name, num_players]) end def init([table_name, num_players]) do children = [ worker(Poker.Table, [table_name, num_players]) ] supervise children, strategy: :one_for_one end end
把这个Supervisor添加到顶层的Supervisor下:code
def start(_type, _args) do import Supervisor.Spec children = [ worker(Poker.Bank, []), supervisor(Poker.Table.Supervisor, [:table_one, 6]) ] opts = [strategy: :one_for_one, name: GenPoker.Supervisor] Supervisor.start_link(children, opts) end
咱们不但愿牌局在玩家准备好以前自动启动,也不但愿在牌局结束以后重启。首先,向 Table Supervisor 中添加一个函数:游戏
def start_hand(supervisor, table, players, config \\ []) do Supervisor.start_child(supervisor, supervisor(Poker.Hand.Supervisor, [table, players, config], restart: :transient, id: :hand_sup ) ) end
咱们使用了 transient 暂时策略,也就是它不会在普通的退出以后被重启。子进程是牌局的Supervisor:进程
defmodule Poker.Hand.Supervisor do use Supervisor def start_link(table, players, config) do Supervisor.start_link(__MODULE__, [table, players, config]) end def init([table, players, config]) do hand_name = String.to_atom("#{table}_hand") children = [ worker(Poker.Hand, [table, players, config, [name: hand_name]], restart: :transient) ] supervise children, strategy: :one_for_one end end
以后咱们会解释多加这一层Supervisor的缘由。咱们须要对Table Supervisor的init稍做修改:get
def init([table_name, num_players]) do children = [ worker(Poker.Table, [self, table_name, num_players]) ] supervise children, strategy: :one_for_one end
以及对deal 发牌消息的 handle_call:
def handle_call(:deal, _from, state = %{hand_sup: nil}) do players = get_players(state) |> Enum.map(&(&1.id)) case Poker.Table.Supervisor.start_hand( state.sup, state.table_name, players ) do {:ok, hand_sup} -> Process.monitor(hand_sup) {:reply, {:ok, hand_sup}, %{state | hand_sup: hand_sup}} error -> {:reply, error, state} end end
咱们在收到deal消息后启动hand牌局,并使用以前建立的Hand Supervisor 来监控。
如今咱们的Supervisor 树已经有了雏形,但咱们的state 状态信息没法保存,它会在进程崩溃时消失。因此咱们须要 ETS 来保存state。当崩溃次数达到必定限度,Supervisor就会放弃,并由上一级Supervisor来重启。
下一篇中,咱们将把已有的程序导入Phoenix Channel 中。