リンクされたプロセスの終了時の挙動 #Erlang

# 概要
このページでは、リンクされたプロセスが終了した時にどのように処理されるかについてまとめている。

# リンクされた **生** プロセスが終了するときの挙動一覧
リンクされたプロセスが終了される時の挙動についての説明は [本：プログラミングErlang](http://www.amazon.co.jp/dp/4274067149) を見るとわかりやすい。
下の表はプログラミングErlangを参考に少し修正している。
（trap_exitで対比できるようにtrap_exitがtrueの時に終了シグナルnormalが飛んできた時を終了シグナルXの場合と分けて書いている。）

| trap_exit | 終了シグナル | プロセスの生死 | 動作 |
|-----------|------------|-------------|------|
| true | normal | 続行する | `{'EXIT', Pid, normal}` をメールボックスに追加する |
| true | kill | 死ぬ | 終了シグナル `killed` をリンクセットにブロードキャストする |
| true | X | 続行する | `{'EXIT', Pid, X}` をメールボックスに追加する |
| false | normal | 続行する | 通常終了シグナルは無視される |
| false | kill | 死ぬ | 終了シグナル `killed` をリンクセットにブロードキャストする |
| false | X | 死ぬ | 終了シグナル `X` をリンクセットにブロードキャストする |

# `exit(Why)` と `exit(self(), Why)` の違い
リンクしているプロセスが受け取るメッセージの終了理由が `kill` と `killed` で異なる。

## `exit(kill)` の場合
リンクしているプロセスは **`kill`** を受け取る

```erl:ex.exit(kill)
> process_flag(trap_exit, true).
false
> spawn_link(fun() -> exit(kill) end).
<0.35.0>
> flush().
Shell got {'EXIT',<0.35.0>,kill}
ok
```

## `exit(self(), kill)` の場合
リンクしているプロセスは **`killed`** を受け取る

```erl:ex.exit(self(),kill)
> process_flag(trap_exit, true).
false
> spawn_link(fun() -> exit(self(), kill) end).
<0.35.0>
> flush().
Shell got {'EXIT',<0.35.0>,killed}
ok
```

## 関数の詳細なドキュメント
上の関数の挙動の違いは公式ドキュメントを読むと書かれている。
（ `kill` が `killed` に変わるということが `exit/2` にしか書かれていないことより。）
また、プログラミングErlangのドキュメントも載せておく。

### `exit/1`
```erlang:exit/1
-spec exit(Reason) -> no_return() when
      Reason :: term().
```

#### 公式ドキュメント（和訳）
終了理由 `Reason` で呼び出しプロセスの実行を停止させる。 `Reason` はどんな項でも良い。この関数の評価はプロセスを終了させるため、戻り値はない。

#### プログラミングErlang
現在のプロセスを要因 `Reason` で終了させる。この文を実行している節が `catch` 文のスコープの中にない場合は、現在リンクされているすべてのプロセスに対して引数 `Reason` を伴う終了シグナルをブロードキャストする。
（プログラミングErlangより。 `Why` を `Reason` に置き換えている。）

### `exit/2`
```erlang:exit/2
-spec exit(Pid, Reason) -> true when
      Pid :: pid() | port(),
      Reason :: term().
```

#### 公式ドキュメント（和訳）
`Pid` で識別されるプロセスまたはポートに終了理由 `Reason` で終了信号を送信する。

`Reason` が `normal` か `kill` 以外の任意の項である場合は、次の動作が適用される。

`Pid` が終了を捕捉していない場合は、 `Pid` 自体が終了理由 `Reason` で終了する。 `Pid` が終了を捕捉している場合は、終了シグナルがメッセージ `{'EXIT', From, Reason}` に変換され、 `Pid` のメッセージキューに追加される。 `From` は終了シグナルを送ったプロセスのpidである。 [`process_flag/2`](http://www.erlang.org/doc/man/erlang.html#process_flag-2) も参照。

`Reason` がアトム `normal` の場合、 `Pid` は終了しない。終了を補足している場合、終了シグナルはメッセージ `{'EXIT', From, normal}` に変換されメッセージキューに追加される。

`Reason` がアトム `kill` の場合、つまり `exit(Pid, kill)` が呼ばれた場合、補足されない終了シグナルが送られ、終了理由 `killed` で無条件に終了する。

#### プログラミングErlang
要因が `Why` の終了シグナルをプロセス `Pid` に送信する。

# gen_serverを停止させるときの挙動一覧
リンクされたgen_serverについて、 `exit` 時の状態、 `exit` の送り主によって「何も呼ばれずに死ん」だり、「 `terminate/2` が呼ばれ」たり、「 `handle_info/2` で補足し」たりするので、ここでまとめておく。

ここで `Srv` はgen_serverのプロセスID（ `pid()` ）である。

| trap_exit | 終了処理 | 送り主 | 動作 | リンクプロセスへの終了シグナル | ERROR REPORTの出力 |
|-----------|---------|-------|-----|---------------------------|-------------------|
| false | `exit(Srv, normal)` | 親プロセス | 死なない | - | なし |
| false | `exit(Srv, kill)` | 親プロセス | 死ぬ | `killed` | なし |
| false | `exit(Srv, X)` | 親プロセス | 死ぬ | `X` | なし |
| false | `Srv ! {'EXIT', self(), normal}` | 親プロセス | `terminate/2` の呼び出し | `normal` | なし |
| false | `Srv ! {'EXIT', self(), kill}` | 親プロセス | `terminate/2` の呼び出し | `kill` | **あり** |
| false | `Srv ! {'EXIT', self(), X}` | 親プロセス | `terminate/2` の呼び出し | `X` | **あり** |
| true | `exit(Srv, normal)` | 親プロセス | `terminate/2` の呼び出し | `normal` | なし |
| true | `exit(Srv, kill)` | 親プロセス | 死ぬ | `killed` | なし |
| true | `exit(Srv, X)` | 親プロセス | `terminate/2` の呼び出し | `X` | **あり** |
| true | `Srv ! {'EXIT', self(), normal}` | 親プロセス | `terminate/2` の呼び出し | `normal` | なし |
| true | `Srv ! {'EXIT', self(), kill}` | 親プロセス | `terminate/2` の呼び出し | `kill` | **あり** |
| true | `Srv ! {'EXIT', self(), X}` | 親プロセス | `terminate/2` の呼び出し | `X` | **あり** |
| false | `exit(Srv, normal)` | 親以外のプロセス | 死なない | - | なし |
| false | `exit(Srv, kill)` | 親以外のプロセス | 死ぬ | `killed` | なし |
| false | `exit(Srv, X)` | 親以外のプロセス | 死ぬ | `X` | なし |
| false | `Srv ! {'EXIT', self(), normal}` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| false | `Srv ! {'EXIT', self(), kill}` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| false | `Srv ! {'EXIT', self(), X}` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| true | `exit(Srv, normal)` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| true | `exit(Srv, kill)` | 親以外のプロセス | 死ぬ | `killed` | なし |
| true | `exit(Srv, X)` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| true | `Srv ! {'EXIT', self(), normal}` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| true | `Srv ! {'EXIT', self(), kill}` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |
| true | `Srv ! {'EXIT', self(), X}` | 親以外のプロセス | `handle_info/2` の呼び出し | - | なし |

# `gen_server:stop/1,3`
`gen_server:stop/1,3` はErlang/OTP 18で追加された関数である。
この関数も停止処理に使うものということでここに公式ドキュメントを和訳しておく。
公式ドキュメントは以下のURLにある。
http://www.erlang.org/doc/man/gen_server.html#stop-1
http://www.erlang.org/doc/man/gen_server.html#stop-3

## spec

```erlang:stop/1,3
-spec stop(ServerRef) -> ok when
      ServerRef :: Name | {Name, Node} | {global, GlobalName} | {via, Mdule, ViaName} | pid(),
      Node :: atom(),
      GlobalName :: term(),
      ViaName :: term().
      
-spec stop(ServerRef, Reason, Timeout) -> ok when
      ServerRef :: Name | {Name, Node} | {global, GlobalName} | {via, Mdule, ViaName} | pid(),
      Node :: atom(),
      GlobalName :: term(),
      ViaName :: term(),
      Reason :: term(),
      Timeout :: pos_integer() | infinity.
```

## 関数の説明（和訳）

与えられた `Reason` で汎用サーバに終了させる命令を送り、終了を待つ。gen_serverは終了する前に [`Module:terminate/2`](http://www.erlang.org/doc/man/gen_server.html#Module:terminate-2) を呼ぶ。

サーバが期待される理由で終了した場合、関数は `ok` を返す。 `normal` 、 `shutdown` 、 `{shutdown, Term}` 以外の理由の場合、 [`error_logger:format/2`](http://www.erlang.org/doc/man/error_logger.html#format-2) を使ってerror reportが発生する。デフォルトの `Reason` は  `normal` である。

`Timeout` は0以上の整数であり、サーバが終了するのを待つまでのミリ秒単位で指定する。または、アトム `infinity` を指定すると永遠に待ち続ける。デフォルトの値は `infinity` である。サーバが指定した時間で終わらなかった場合は、 `timeout` 例外が発生する。

プロセスが存在しない場合は、 `noproc` 例外が発生する。

# `link/1` の挙動（ `PidOrPort` が存在しない場合の挙動）
リンク時の状態によって、リンク先のプロセス `PidOrPort` がない場合の挙動が異なるので、ここに公式ドキュメントを和訳しておく。

## 公式ドキュメント（和訳）
既に呼び出しプロセスと別のプロセス（またはポート） `PidOrPort` との間にリンクがなければ、そのリンクを作る。プロセスは自身へのリンクを作成しようとした時は何もしない。 `true` を返す。

*`PidOrPort` が存在しない場合*、BIFの振る舞いは **呼び出しプロセスが終了を補足しているか** どうかによる（ [`process_flag/2`](http://www.erlang.org/doc/man/erlang.html#process_flag-2) を参照）:

* *呼び出しプロセスが終了を補足していない、かつ、 `PidOrPort` のチェックがチープな場合（つまり、 `PidOrPort` がローカルにある場合）*、 `link/1` は理由 **`noproc` で失敗** する。
* *そうでない場合は、呼び出しプロセスが終了を補足している場合、かつ／または、 `PidOrPort` がリモートにある場合*、 `link/1` は `true` を返すが、理由 **`noproc` を伴う終了シグナルが呼び出しプロセスに送られる。**