A<->B<->C な gen_server groupを作成する

# A<->B<->C な gen_server groupを作成する

ある日アニメを見ていたら, twitterで面白そうなお題（？）が流れてきました.

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Erlang のプロセス構成で最大の課題にぶつかっていてツライ。どうするかなー。A &lt;-&gt; B &lt;-&gt; C というプロセス構成はなかなか大変だ。いい案がないな。</p>&mdash; V (@voluntas) <a href="https://twitter.com/voluntas/status/743476629742116864">2016年6月16日</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

自分も2, 3回こういった構成にせざるを得なくなり, 名前付けで逃げていました.
しかし, 起動タイミングの問題などで名前付けだけでは

```erlang
init(_) ->
    {ok, {{one_for_all, 5, 10},
          [
            %% NOTE: Bよりも先に起動しなければならない
            {module_a, {module_a, start_link, []}, permanent, 5000, worker, [module_a]},
            {module_b, {module_b, start_link, []}, permanent, 5000, worker, [module_b]}
          ]}.
```

のようにイケてない書き方をせざるを得なくなっていました. 本当にイケてません.
何がイケてないって, 起動直後にmodule_b -> module_aへの通信をしなければいけなくなった時に頭を抱えるのでイケてません (経験談)


ということで, イケてるやり方を思いついたので紹介します.

## 結論

```erlang
init(SupPid) ->
    ok = gen_server:cast(self(), {sync, SupPid}),
    {ok, State}.

handle_cast({sync, SupPid}, State) ->
    Pids = [Child || {_Id, Child, _, _} <- supervisor:which_children(SupPid)],
    {noreply, State#?state{group_member = Pids}}.
```

- `one_for_all`のみを考慮しています.
- 名前付けをする必要はなく, ChildIdと`pid/0`が分かれば良いものとします.

## 解説

```
supervisor       |    gen_server (A)    |     gen_server (B)
------------------------------------------------------------------
start_link
    ↓
  init -------(a)------> init
       <------(a)------    |
                           ↓
       <------(b)------ handle_cast

       ----------------(c)------------------>  init
       <---------------(c)-------------------    |
                                                 ↓   
       <---------------(d)------------------- handle_cast

       ----(b)----> (which_children reply)

       ----------------(d)----------------> (which_children reply)
```

### gen_server の init処理

- `start_link`の返値が返る前に`init`は返値を返している.
- `init`は新しく起動されたプロセス上で実行される.

この仕様から, init処理が重い場合に`self()`にメッセージを送る事で, initの直後に実行する関数を指定することができます.

※ `erlang:processes()`などの全体のプロセスを知る方法を使うとこの制約はなくなりますが, 調査以外でまず使うことはないので, この前提で問題が起きることはないはずです.

### supervisor の 子の起動の仕方

- `one_for_all`, `rest_for_one`の場合, 同時に再起動する対象について, 0 or N が`supervisor`の各APIが返すタイミングでは保証される (他の場合は, 同時であることを指定していない)
- `start_child`の際に, 子の起動 (initの返値を得る) までは`supervisor`は他の動作をしない.

`SupervisorModule:init`で指定された複数の子は順番に起動されるが, 全部の起動が終わるまでは他の処理を受け付けません.
これによって, `supervisor:which_children`を返すことができるようになった時点で全ての子が「いる or いない」が保証できます.
また, 子自身が`supervisor:which_children`を呼び出した場合は前者が保証されます. (replyを得る前にsupervisorによってkillされる可能性はあります)

### `supervisor:which_children` の timeout
https://github.com/erlang/otp/blob/OTP-19.0-rc2/lib/stdlib/src/supervisor.erl#L235-L236
supervisorは全てのcallで`infinity`を使用しています (OTP19.0-rc2時点)
その為, `which_children`でtimeoutになることはありません.

## 検証

理論的にはこれで動くので, 動作確認がてら[汎用ライブラリ gcyclic](https://github.com/soranoba/gcyclic) を作りました.
検証コードは[こちら](https://github.com/soranoba/gcyclic/blob/v0.1.0/ct/gcyclic_SUITE.erl#L47-L62)

確認しているのは,

- 起動時にどのプロセスも残りの全プロセスの情報を取得できていること
- 再起動時にもどうようのことが成り立つこと

だけですが.

## 捕捉

### 其の壱

`one_for_one`や`rest_for_one`もこれを拡張することで実現はできそうです. ただ, 一部が存在しない状況を考慮するぐらいなら`one_for_all`にしてしまう方が良さそうです.

### 其の弐

この方法ではできなくなることが一つだけ存在します.
それは, ホットコードアップデートで`supervisor`の`strategy`を変更することです.
まぁ, まずやらないので大丈夫でしょう ^^;;;