(整理)用Elixir作一個多人撲克遊戲 3

今天咱們將爲德州撲克遊戲添加故障恢復能力。app

OTP爲咱們準備好了構建容錯程序所須要的工具。咱們只須要定義正確的behavior 行爲。函數

Supervisor

有了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>

The Table Supervisor

咱們能夠把牌桌和牌局進程放在同一個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

添加hand 牌局

咱們不但願牌局在玩家準備好以前自動啓動,也不但願在牌局結束以後重啓。首先,向 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 中。

相關文章
相關標籤/搜索