ノードの名前を変えずに再起動した場合に、再起動前に他のノードに送信済みの古いpid()
やreference()
が、再起動後に生成された新しいものと衝突しないかが気になったので少し調べてみた。
Erlangがノード間通信の際に使用するシリアライズ形式(外部形式)に関するドキュメント(External Term Format)を見てみると、pid()
やport()
、reference()
といったID系の型には、最後に一バイトのCreationフィールドといったものが存在していることが分かる (ただし領域な範囲はその中の2bitのみ)。
例えばpid()
の外部形式をバイナリ表記で示すと<<Tag:8, NodeName:N, Id:32, Serial:32, Creation:8>>
となる。
このフィールドの値はノードが再接続する度に(?)インクリメントされ、冒頭に書いたような再起動等に纏わる衝突問題を回避するための使用される模様。
実際に試してみる。
$ erl -sname master@localhsot
% 別ノードを起動
> {ok, _} = slave:start_link(localhost, slave).
> nodes().
[slave@localhost]
% slave@localhost上でプロセスを起動してPIDを取得する
> P0 = spawn(slave@localhost, fun () -> ok end).
<7227.41.0>
% ノード停止
> ok = slave:stop(slave@localhost).
> nodes().
[]
% 上記の流れを関数化
> RemotePid = fun () ->
{ok, _} = slave:start(localhost, slave),
P = spawn(slave@localhost, fun () -> ok end),
ok = slave:stop(slave@localhost),
P
end.
% 再起動後のslave@localhost上のプロセスのPIDを取得
> P1 = RemotePid().
<7227.41.0>
% P0 と P1 は見た目上は同一だが、実態は異なる
> P0 =:= P1.
false
% `term_to_binary/1`を適用してみると`Creation(末尾1byte)`の値が異なっていることが分かる
> term_to_binary(P0).
<<131,103,100,0,15,115,108,97,118,101,64,108,111,99,97,
108,104,111,115,116,0,0,0,41,0,0,0,0,1>>
> term_to_binary(P1).
<<131,103,100,0,15,115,108,97,118,101,64,108,111,99,97,
108,104,111,115,116,0,0,0,41,0,0,0,0,2>>
% もう三回再起動してみる
> P2 = RemotePid().
> P3 = RemotePid().
> P4 = RemotePid().
% P0と比較: 2bit分しかないので五回目で一周している
> [P0 =:= P || P <- [P1, P2, P3, P4]].
[false, false, false, true].
> term_to_binary(P4).
<<131,103,100,0,15,115,108,97,118,101,64,108,111,99,97,
108,104,111,115,116,0,0,0,41,0,0,0,0,1>>
Creationが2bit分しか使われないのは若干心もとなくはあるけど、基本的にPIDの衝突(偶然の一致)が問題になりそうなのはノード起動直後に生成された一時プロセスくらい(長命なプロセスは大抵linkやmonitorで監視が行われノードダウン時に一緒に破棄される)だと思うので、よほど頻繁にノード再起動を行うようなケースでもなければあまり実害はなさそう。
(永続的に保持されるようなreference()
がもしある場合には気を付ける必要があるかもしれない)