ヽ(´・肉・`)ノログ

How do we fighting without fighting?

標準入力をechoするGenServer

Elixirでshellのように標準入力を待ち,quitと入力されたら処理を抜けるようなGenServerの作り方.

defmodule EchoShell do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init(_args) do
    {:ok, task} = Task.start_link(do_gets(self))

    {:ok, task}
  end

  def handle_info("quit", state) do
    {:stop, :normal, state}
  end

  def handle_info(msg, _state) do
    IO.puts msg
    {:ok, task} = Task.start_link(do_gets(self))

    {:noreply, task}
  end

  defp do_gets(pid) do
    fn ->
      # trim whitespace around a line
      line = String.trim(IO.gets("> "))
      send pid, line
    end
  end
end

大事なところは以下のようになる

実行してみるとこんな感じだ.

% elixir --no-halt -r echo_shell.exs -e "EchoShell.start_link"
> foo
foo
> bar
bar
> quit
aaa
bbb
^C
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^C%

quit 以外では Echo して, quit で終了して,その後は受け付けていないのがわかるだろう.

GenServer にしておくと,Supervisor をつけられて,そうすると shell が終了しても自動的に再起動してくれるのがいいところだ.

defmodule EchoShell2 do
  use GenServer

  def start_link do
    GenServer.start_link(__MODULE__, [], name: __MODULE__)
  end

  def init(_args) do
    {:ok, task} = Task.start_link(do_gets(self))

    {:ok, task}
  end

  def handle_info("quit", state) do
    {:stop, :normal, state}
  end

  def handle_info("boom", _state) do
    raise "boom!!!"
  end

  def handle_info(msg, _state) do
    IO.puts msg
    {:ok, task} = Task.start_link(do_gets(self))

    {:noreply, task}
  end

  defp do_gets(pid) do
    fn ->
      # trim whitespace around a line
      line = String.trim(IO.gets("> "))
      send pid, line
    end
  end
end
% elixir -r echo_shell2.exs -e "import Supervisor.Spec; Supervisor.start_link([worker(EchoShell, [])], strategy: :one_for_one); Process.sleep(:infinity)"
> foo
foo
> bar
bar
> boom
>
12:52:27.715 [error] GenServer EchoShell terminating
** (RuntimeError) boom!!!
    echo_shell2.exs:19: EchoShell.handle_info/2
    (stdlib) gen_server.erl:601: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:667: :gen_server.handle_msg/5
    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3
Last message: "boom"
State: #PID<0.79.0>
> baz
baz
> quit
> aaa
aaa
> bbb
bbb
> ^C
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution
^C%

このやり方でちょっとエラーになっても初期化して再開してくれる基盤の上で,ブロッキングするような処理,無限ループするような処理を動かせるようになる.

このエントリーをはてなブックマークに追加