(整理)用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 中。

相关文章
相关标签/搜索