【TIPS】プロセスの名前付けと検索①【シングルクラスタ編】

この記事は、[Elixir Advent Calendar 2023 シリーズ14](https://qiita.com/advent-calendar/2023/elixir) の5日目です

---

[piacere](https://twitter.com/piacere_ex) です、ご覧いただいてありがとございます :bow:

Elixirは、様々な方法でプロセスの名前付けができるので、これらがどの位、相互運用できるかを確認するため、名前付けとその検索について探ります

今回は、シングルクラスタに限定しています

# 名前付けの方法

Elixirのプロセス生成や名前付けは、沢山のバリエーションがあるので、1つずつチェックしていきます

## ① Process.register【動的】

`spawn` でプロセス名は付けられませんが、後で、`Process.register` すれば、名前付けできます

```diff_elixir
iex> pid = spawn(fn -> receive do n -> IO.puts("spawn: #{n}") end end)
#PID<0.162.0>
+iex> Process.register(pid, :named_process)
+true
```

```diff_elixir:プロセス名での呼び出し
iex> send(:named_process, "hoge")
spawn: hoge
"hoge"
```

`Process.info` でプロセス名取得できます

```diff_elixir:プロセス名の取得①
iex> Process.info(pid)
[
+ registered_name: :named_process,
  current_function: {:prim_eval, :receive, 2},…
```

`Process.registered` でも拾えます

```diff_elixir:プロセス名の取得②
iex> Process.registered
[:kernel_sup, :inet_db, Mix.TasksServer, :kernel_refc, IEx.Supervisor, IEx.Pry,
 :logger, Logger.Supervisor, :logger_std_h_default, :logger_handler_watcher,
 Mix.ProjectStack, :file_server_2, :elixir_sup, :user_drv, :standard_error_sup,
+Mix.State, :user, :code_server, :socket_registry, :named_process,
…
```

`Process.unregister` で名前付けを解除できます

```diff_elixir:プロセス名の登録解除
iex> Process.unregister(:named_process)
true
iex> Process.info(pid)                 
[
- registered_name: :named_process,
  current_function: {:prim_eval, :receive, 2},…
…
```

## ② GenServer.start／start_link【動的】

`GenServer.start` や `GenServer.start_link` の第三引数に `:name` を指定することで名前付けできます

```diff_elixir
defmodule Named do
  use GenServer
  def init(_), do: {:ok, nil}
+ def start(_, _),  do: GenServer.start_link(__MODULE__, "", name: :named_process)
+ def start_link(), do: GenServer.start_link(__MODULE__, "", name: :named_process)
  def handle_call(:now, _, _), do: {:reply,   NaiveDateTime.utc_now, nil}
  def handle_info(_, _),       do: {:noreply, IO.puts("info: #{NaiveDateTime.utc_now}")}
end
```

```diff_elixir:プロセス名での呼び出し
iex> Named.start_link()
iex> send(:named_process, "dummy")
info: 2024-01-10 13:10:39.331147
"dummy"
```

GenServerも `Process.info` でプロセス名取得できます（`Process.unregister` で名前付け解除もできます）

```diff_elixir:プロセス名の取得
iex> Process.info(pid)
[
+ registered_name: :named_process,
  current_function: {:gen_server, :loop, 7},
…
```

なお、GenServerをベースとしている下記もこれらは同様です

- Task
- Agent
- Registry
- Supervisor
- Logger
- Broker
- ETS
- Mnesia
- GenTcp
- GenFsm
- GenEvent
- GenStatem
- GenStage

## ③ :global.register_name【動的】

複数クラスタ間でプロセス共有できる `:global.register_name` でも、名前付けできます

`Process.info` と引数の順番が逆なので、気を付けてください

```diff_elixir
iex> pid = spawn(fn -> receive do n -> IO.puts("spawn: #{n}") end end)
#PID<0.162.0>
+iex> :global.register_name(:named_process, pid)
+:yes
```

`:global.whereis_name` でプロセス名からプロセスIDを取得できます

```diff_elixir:プロセス名での呼び出し
iex> send(:global.whereis_name(:named_process), "hoge")
spawn: hoge
"hoge"
```

`:global.registered_names` でプロセス名取得できます

```diff_elixir:プロセス名の取得①
iex> :global.registered_names
[:named_process]
```

しかし、`Process.info` では、`:global.register_name` のプロセス名が取得できません

```diff_elixir:プロセス名の取得②
iex> Process.info(:global.whereis_name(:named_process))               
[
  current_function: {:prim_eval, :receive, 2},
  initial_call: {:erlang, :apply, 2},
  status: :waiting,
  message_queue_len: 0,
  links: [],
  dictionary: [],
  trap_exit: false,
  error_handler: :error_handler,
  priority: :normal,
  group_leader: #PID<0.66.0>,
  total_heap_size: 376,
  heap_size: 376,
  stack_size: 9,
  reductions: 4007,
  garbage_collection: [
    max_heap_size: %{error_logger: true, kill: true, size: 0},
    min_bin_vheap_size: 46422,
    min_heap_size: 233,
    fullsweep_after: 65535,
    minor_gcs: 0
  ],
  suspending: []
]
```
  
## ④ mix.exsのmod【アプリケーション起動時】

`mix.exs` で `mod` を指定しておけば、`Process.register` や `start_link` 等で名前付けをしなくても名前付きプロセスを使えます

```diff_elixir:mix.exs
defmodule Named.MixProject do
  use Mix.Project
…
  # Run "mix help compile.app" to learn about applications.
  def application do
    [
+     mod: {Named, nil},
      extra_applications: [:logger]
    ]
  end
…
```

利用時は、`Process.register` や `start_link` 等で名前付けしたケースと同様です

```diff_elixir:プロセス名での呼び出し
iex> send(:named_process, "hoge")
spawn: hoge
"hoge"
```

なお、このテクニック、[**書籍「プログラミングElixir」**](https://www.amazon.co.jp/exec/obidos/ASIN/4274226379/onelinebiz-22) のP278（第一版だとP220）で解説されているのですが、マイクロサービスやエッジコンピューティング、組み込みでは重要なテクニックになります :wink: 

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/73019c11-dc0d-9900-e06b-1d188240a4ae.png)](https://www.amazon.co.jp/exec/obidos/ASIN/4274226379/onelinebiz-22)

[![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155423/a4e2076c-8106-51d1-ab0a-59f63dc59a2e.png)](https://www.amazon.co.jp/exec/obidos/ASIN/4274219151/onelinebiz-22)

# 検索の方法

## ａ）Process.whwereis

`Process.register`、`GenServer.start／start_link`、mix.exsのmodで作られたプロセス名は特定でき、`:global.register_name` は特定不可のようです

### ① Process.register … 特定できる

```diff_elixir
iex> pid = spawn(fn -> receive do n -> IO.puts("spawn: #{n}") end end)
iex> Process.register(:named_process)
iex> Process.whereis(:named_process)
#PID<0.131.0>
```

### ② GenServer.start／start_link … 特定できる

```diff_elixir
iex> Named.start_link()
iex> Process.whereis(:named_process)
#PID<0.131.0>
```

### ③ :global.register_name … 特定不可

```diff_elixir
iex> pid = spawn(fn -> receive do n -> IO.puts("spawn: #{n}") end end)
iex> :global.register_name(:named_process, pid)
iex> Process.whereis(:named_process)
nil
```

### ④ mix.exsのmod … 特定できる

```diff_elixir
iex> Process.whereis(:named_process)
#PID<0.131.0>
```

## ｂ）:global.whereis_name

`:global.register_name` は特定でき、`Process.register`、`GenServer.start／start_link`、mix.exsのmodで作られたプロセス名は特定不可のようです

### ① spawn＋Process.register … 特定不可

```diff_elixir
iex> pid = spawn(fn -> receive do n -> IO.puts("spawn: #{n}") end end)
iex> Process.register(pid, :named_process)
iex> :global.whereis_name(:named_process)
:undefined
```

### ② GenServer.start／start_link … 特定不可

```diff_elixir
iex> Named.start_link()
iex> :global.whereis_name(:named_process)
:undefined
```

### ③ :global.register_name … 特定できる

```diff_elixir
iex> pid = spawn(fn -> receive do n -> IO.puts("spawn: #{n}") end end)
iex> :global.register_name(:named_process, pid)
iex> :global.whereis_name(:named_process)
#PID<0.131.0>
```

### ④ mix.exsのmod … 特定不可

```diff_elixir
iex> :global.whereis_name(:named_process)
:undefined
```