今天咱們將爲德州撲克遊戲添加故障恢復能力。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 中。