gproc (Erlang register拡張ライブラリ)

# gproc (Erlang register拡張ライブラリ) の使い方
[mururuさんが最近簡単な使い方を投稿してくださった](http://qiita.com/mururu/items/2fa4f06b8f3572be4b28)影響かは分からないのですが、周りでgprocを使う人が出てきたのでちょっと触ってみようと思ったら意外と詰まったので, `select`系以外をざっと調べてみた.

一応実際に検証しながら書いているが, 間違いがあるかもしれないので, その際は公式を参照して欲しい.

syntax_suger？的な関数は省略していますので悪しからず.

レポジトリ|[uwiger/gproc](https://github.com/uwiger/gproc)|
---|---|
バージョン|0.3.1|
ライセンス| Erlang Public License|

## 基本関数
### reg/1, reg/2 <a name=reg>
```erlang
reg(Key :: key()) -> true.
reg(Key :: key(), Value :: term()) -> true.
```
プロセスを登録する.

```erlang
type()  :: n | p | c | a. %% n: name, p: property, c: counter, a: aggregate_counter
scope() :: l, g.          %% l: local, g: global
key()   :: {type(), scope(), any()}
```
`n`はユニークな名前. `p`は複数のプロセスに割り当てることができるタグ.
`c`はタグ (`p`)と同様に使用することができ, 加えてValueを`integer()`のみに制限することで[counterの機能](#counter)を提供している.
`a`は`n`と同様ユニークであるが, counterの機能は使えない為　使用用途が不明だった.

### unreg/1
```erlang
unreg(Key :: key()) -> true.
```
`key/0`で`reg/1` (`reg/2`)した名前の登録を削除する.
呼び出し元のプロセスの登録情報のみ削除可能.

### goodbye/0
```erlang
goodbye() -> ok.
```
呼び出し元のプロセスの`reg`情報を全て削除する.

### where/1, lookup_pids/1, lookup_pid/1
```erlang
where(Key :: key())       -> pid() | undefiend.
lookup_pid(Key :: key())  -> pid().
lookup_pids(Key :: key()) -> [pid()].
```
`key`で`gproc:reg/1`された`pid/0`を取得する.
`{p | c, _, _}`の場合は`lookup_pids/1`を、それ以外の場合はどれでも利用できる.

## reg/unreg拡張
### mreg/3
```erlang
mreg(type(), scope(), [{Key :: any(), Value :: any()}]) -> true. 
```
`type() = n`の場合は, 1つでの登録できないものがあれば全て登録しない.
`reg`を複数回実行するよりも, `gproc`の`gen_server`への`call`が1回しか行われない為, 高速.

引数については[reg](#reg)を参照
### munreg/3
```erlang
munreg(type(), scope(), [Key :: any()]) -> true.
```
同時に`unreg`する. 指定した`key`を全て`unreg`の状態にする. (一部が`reg`されていなくても問題ない)

### reg_shared/1, reg_shared2 <a name=reg_shared>
```erlang
reg_shared(Key :: key())        -> true.
reg_shared(Key :: key(), Value) -> true.
```
特定のプロセスに紐付けない共有メモリのような使用ができる.変更をするのは`gproc`のプロセスの為, スレッドセーフである.

### reg_or_locate/1, reg_or_locate/2, reg_or_locate/3
```erlang
reg_or_locate(Key :: key())                               -> {pid(), Value}.
reg_or_locate(Key :: key(), Value)                        -> {pid(), Value}.
reg_or_locate(Key :: key(), Value, fun(() -> no_return()) -> {pid(), Value}.
```
`key/0`には`{n, _, _}`のみ利用できる.
もし`key/0`が存在しない場合はプロセスを作成する場合に利用.
`reg_or_locate/3`は存在しなかった場合, 第3引数を`spawn`する.

返り値は, 

- 存在した場合　：登録されている`pid/0`, `Value`情報
- 存在しない場合：登録した`pid/0`, `Value`情報

### give_away/2
```erlang
give_away(From :: key(), To :: pid() | key()) -> undefined | pid().
```
呼び出し元プロセスにつけられた`key`を委譲する.

`From = {_, l, _}`の場合は`To = pid() | {n, l, _}` (`pid()`の場合は`node(Pid) == node()`) のみ指定可能.
`From = {_, g, _}`の場合は`To = pid() | {n | a, g, _}`のみ指定可能.

## プロセス監視関数
### monitor/1, monitor/2
```erlang
%% monitor(Key)          @equiv monitor(Key, info)
monitor(Key :: key(), Type :: monitor_type()) -> reference().
```

```erlang
monitor_type() = info | standby | follow.
```
`key() = {n | a, _, _}`のみ指定可能.

送信されるメッセージは4種類
1. `{gproc, unreg, Ref, Key}`
2. `{gproc, {migrated, ToPid}, Ref, Key}`
3. `{gproc, {failover, ToPid}, Ref, Key}`
4. `{gproc, registered, Ref, Key}`

`monitor_type`による挙動の違いは以下の通り

- 共通
 - `monitor`した際に登録されていない場合は1のメッセージが即座に送出される.
 - `give_away/2`が行われた場合, 2のメッセージが送出される.
 - 1の送出条件を満たした場合に`monitor`は終了する.
- info
 - 共通の挙動のみ.
- standby
 - 1のメッセージ送出条件を満たした時, 呼び出し元プロセスに名前の委譲を行う. 実際に送出されるのは3のメッセージ.
- follow
 - 1のメッセージ送出条件を満たしても`monitor`は終了せず, 継続する.
 - 新しくプロセスが割り当てられた場合4のメッセージが送出される.

### demonitor/2
```erlang
demonitor(Key :: key(), Ref :: reference()) -> ok.
```
`key`には`{n | a, _, _}`のみ指定可能.

### await/1, await/2, await/3
```erlang
%% await(Key)           @equiv await(node(), Key, infinity)
%% await(Key, Timeout)  @equiv await(node(), Key, Timeout)
await(node(), key(), Timeout :: non_neg_integer() | infinity) -> {pid(), Value}
```
指定した`key`で`reg/1`(`reg/2`)されるのを待つ.
既に登録されている場合は, すぐに返す.
`key`には`{n, _, _}`のみ指定可能.

### wide_await/3
```erlang
wide_await(Nodes :: [node()], Key :: key(), Timeout :: non_neg_integer() | infinity) -> {pid(), Value}.
```
`await`の拡張. 複数ノード上で監視を行う.
`key`には`{n, l, _}`のみ指定可能.

### nb_wait/1, nb_wait/2
```erlang
%% nb_wait(Key)         @equiv nb_wait(node(), Key)
nb_wait(Node :: node(), Key :: key()) -> reference(). 
```
`await`のノンブロッキング版.
`key`には`{n, _, _}`のみ指定可能.
プロセス作成時に`{gproc, reference(), registered, {key(), pid(), Value}}`のメッセージが送信される.
既に作成されている場合でも同様のメッセージが送られる.

### cancel_wait/2, cancel_wait/3
```erlang
cancel_wait(Key :: key(), Ref :: all | reference()) -> ok.
cancel_wait(Node :: node(), Key :: key(), Ref :: all | reference()) -> ok.
```
`nb_wait`をキャンセルする.
`{_, g, _}`を指定する場合は`cancel_wait/2`を指定する.

### audit_process/1
```erlang
audit_process(Pid :: pid()) -> ok.
```
`is_process_alive/1`がfalseの場合は`reg/1`されている情報を削除する.
必要がなければ使うべきではない.

## メッセージ送信関数
### send/2
```erlang
send(Process | key(), Msg) -> Msg when
    Process :: pid() | atom() | {pid() | atom(), node()},
    Msg     :: any().  
```
`Pid ! Msg`と同等の機能.
但し, `type() = p | c`の場合は該当するプロセス全てにブロードキャストする.
*keyにwildcardを含んではならない*

### bcast/2, bcast/3
```erlang
%% bcast(Key, Msg)       @equiv bcast(nodes(), Key, Msg)
bcast(Nodes :: [atom()], Key :: key(), Msg) -> Msg when
    Msg :: any().
```
指定した`node()`上の`key()`のプロセスに対してブロードキャストする.
`gen_server:abcast/3`を内部で使用.
`key`には`{_, l, _}`のみ指定可能.

## Counterに関する関数<a name=counter>
### reset_counter/1
```erlang
reset_counter(Key :: key()) -> {ValueBefore, ValueAfter}.
```
`key() = {c, _, _}`のみ指定可能.
`reg`した際の値に戻す.

### update_counter/2, update_counter/3
```erlang
update_counter(Key :: key(), Incr :: increment())                        -> integer() | [integer()].
update_counter(Key :: key(), Pid :: pid() | shared, Incr :: increment()) -> integer() | [integer()].
```
```erlang
ctr_update() :: {Incr :: integer(), Threshold :: integer(), SetVal :: integer()}.
increment()  :: integer() | ctr_update() | [integer() | ctr_update()].
```
`ctr_update/0`は, Thresholdを超えた場合, `SetVal`が代入される.
`Incr`にリストを指定した場合は, foldlを実行し, その途中結果を含む結果のリストを返す.
`key() = {n | c, _, _}`のみ指定可能. 但し, nを指定する場合は`reg/2`で値を`integer()`に設定する必要がある.

### update_counters/2
```erlang
update_counters(scope(), [key(), pid() | shared, increment()]) -> [{key(), pid(), integer() | [integer()]}].
```
`key() = {c, _, _}`のみ指定可能.
他は, `update_counter/3`と同様の挙動をする.

## attributes/valuesに関する関数
### set_value/2
```erlang
set_value(Key :: key(), Value) -> true.
```
`reg`した際に設定したValueを変更する. 呼び出し元が`reg`した`key`のみ変更可能.
### get_value/1, get_value/2
```erlang
get_value(Key :: key())               -> Value.
get_value(Key :: key(), Pid :: pid()) -> Value.
```
`{Pid, Key}`に設定されているValueを取得する. デフォルト値は`undefined | 0`

### set_value_shared/2
```erlang
set_value_shared(Key :: key(), Value) -> true.
```
`key = {p | c | a, _, _}`のみ指定可能.
[reg_shared](#reg_shared)の際に設定したValueを変更する.

### get_value_shared/1
```erlang
get_value_shared(Key :: key()) -> Value.
```
`key = {p | c | a, _, _}`のみ指定可能.

### set_attributes/2
```erlang
set_attributes(Key :: key(), Props :: [{Key :: atom(), Value :: any()}]) -> true.
```
呼び出し元プロセスの`key()`に対し, `attribute`をつける. 
つまり, `protected`でアクセス可能な値.

### get_attribute/2, get_attribute/3
```erlang
get_attribute(Key :: key(), Attribute :: atom())        -> Value | undefined.
get_attribute(Key :: key(), pid(), Attribute :: atom()) -> Value | undefined.
```
`set_attribute/2`で設定した値を取り出す.
存在しない`key`を指定した場合は例外が送出され, 値が設定されていない場合は`undefined`になる.

### set_attributes_shared/2
```erlang
set_attributes_shared(Key :: key(), [{Key :: atom(), Value :: any()}]) -> true.
```
`key() = {c | a | p, _, _}`のみ指定可能.
`set_attributes/2`と同様のことを, [sharedで作成した](#reg_shared)keyに対して実行する.

### get_attributes_shared/2
```erlang
get_attributes_shared(Key :: key(), Attr :: atom()) -> Value | undefined.
```
`set_attributes_shared/2`で設定した値を取り出す.
存在しない`key`を指定した場合は例外が送出され, 値が設定されていない場合は`undefined`になる.

## ソースコードを流し読んでみて...
`scope() = g`に関する処理を行う`gproc_dist`は全く読んでないですが, read系の処理に関しては極力呼び出し元のプロセスで行われるので, 速度的に問題になりそうなことはなさそうではある.
ただ, プロセスの終了を検知した際には`gproc`の`gen_server`がつけられた全ての`key`に関する`monitor`などを調べ, 操作を行うので高頻度で終了するプロセスに対してはあまり使わない方いいのかもしれない...

また, いくつかの処理は(例えばgive_away) 行った後でもタイミング差でメッセージが来る場合がある為, 確実性を保証する場面では`pid`を使用するべきなのだろう.逆に不確実で良い場面の方が活躍しそうだ.