LoginSignup
5
3

More than 5 years have passed since last update.

Registry の via_tuple について

Last updated at Posted at 2017-12-09

Elixir 1.4 で入った Registry のドキュメントを読んでいると唐突に出てくる via_tuple についてです。Registry とGenServer のドキュメントをちゃんと読んでいる人にとってはどうでもいい記事です。

参考:
Registry
Registry について

Registry には大きく分けて2つの用途があります。

  1. 動的に立ち上げるプロセスに atom 以外の一意な名前をつける
  2. 複数のプロセスに同じ名前をつけて一括でメッセージを送る

Registryの起動時に 1は:unique, 2 は:duplicateを指定します。
:unique な Registry にプロセス(GenServer)を登録する場合、:name に via_tuple を指定するのが一般的です。

via_tuple

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

defp via_tuple(name) do
  {:via, Registry, {PlayerRegistry, name}}
end

def push(name, message) do
  # 呼び出し側は pid を知らなくてもメッセージを送ることができる
  GenServer.cast(via_tuple(name), message) 
end

なぜこれが動くかというと GenServer.castの第一引数の型 serverは以下のようになっていて atom の他に {:via, module, term} が使えるからです。

gen_server.ex
defmodule GenServer do
  @type server :: pid | name | {atom, node}
  @type name :: atom | {:global, term} | {:via, module, term}
  ...
  @spec call(server, term, timeout) :: term
  def call(server, request, timeout \\ 5000) do
   ...
  end
end

via_tupleに渡すmodule

via_tuple の2要素め, module に Registry を指定しましたが、実は別のモジュールも渡せます。
GenServerのドキュメント をみると

    * `{:via, module, term}` - the GenServer is registered with the given
      mechanism and name. The `:via` option expects a module that exports
      `register_name/2`, `unregister_name/1`, `whereis_name/1` and `send/2`.
      One such example is the [`:global` module](http://www.erlang.org/doc/man/global.html) which uses these functions
      for keeping the list of names of processes and their associated PIDs
      that are available globally for a network of Elixir nodes. Elixir also
      ships with a local, decentralized and scalable registry called `Registry`
      for locally storing names that are generated dynamically.

:via に渡すモジュールは以下の4つの関数を実装しているモジュールであればなんでもいいようです。

  • register_name/2
  • unregister_name/1
  • whereis_name/1
  • send/2

ということは Erlang のglobal モジュールや gproc も使えますし 意味があるかはさておき Process Registry を自作することもできます。

gen.erl

gen_server.exの該当箇所を読むと、この{:via, module, name} でプロセスをひく機能自体は Erlang の gen.erl で提供されているものであることがわかります。gen_server.erl ではなく gen.erl を使っているのは link するかしないかを引数で指定したいからですね。

gen_server.ex
  defp do_start(link, module, args, options) do
    case Keyword.pop(options, :name) do
      {nil, opts} ->
        :gen.start(:gen_server, link, module, args, opts)
      {atom, opts} when is_atom(atom) ->
        :gen.start(:gen_server, link, {:local, atom}, module, args, opts)
      {{:global, _term} = tuple, opts} ->
        :gen.start(:gen_server, link, tuple, module, args, opts)
      {{:via, via_module, _term} = tuple, opts} when is_atom(via_module) ->
        :gen.start(:gen_server, link, tuple, module, args, opts)
      {other, _} ->
        raise ArgumentError, """
        expected :name option to be one of:
          * nil
          * atom
          * {:global, term}
          * {:via, module, term}
        Got: #{inspect(other)}
        """
    end
  end

参考:
Erlang/OTP の gen.erl を読む
gen.erl

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3