常駐している Erlang プロセスのコードを入れ替える事ができる条件

常駐している Erlang プロセスのコードを無停止で動的に入れ替えるには、設計の段階で、入れ替えが発生すると思われる箇所を、プロセスとは異なる別モジュールに切り離しておく必要がある。

常駐している Erlang プロセスのコードの入れ替えに失敗する例

まず、下記のようなモジュールを用意する。

-module(test).

-export([start/0, talk/1]).
-export([loop/0]).

start() ->
  spawn_link(fun loop/0).

talk(Pid) ->
  Pid ! hello,
  ok.

loop() ->
  receive ReceiveMessage -> ok end,
  io:fwrite("~p~n", [get_message(ReceiveMessage)]),
  loop().

get_message(hello) -> hi;
get_message(Other) -> Other.

ErlangVM を起動して動作確認を行なう。

% erl
1> Pid = test:start().
<0.33.0>
2> test:talk(Pid).
hi
ok

この状態で、get_message/1 を下記のように書き換える。

get_message(hello) -> hello;
get_message(Other) -> Other.

ErlangVM でコードの入れ替えを試してみる。

3> l(test).
{module,test}
4> test:talk(Pid).
hi
ok

おや?入れ替わっていない。もう一度、ファイルの保存とコンパイルを行ない、ロードし直してみる。

5> l(test).
{module,test}
6> test:talk(Pid).
ok

あれ?何も帰ってこない?この状態で、i() でプロセスの状態を確認するとプロセスが落ちていた。試しに終了シグナルをトラップしてみた所、killed を受信していた。
ちなみに、talk/1 はプロセスから参照されていない関数である為、入れ替えは可能だが、何度も書き換えてロードすると、やはりプロセスは死んでしまった。
何か入れ替えの手順が抜けているのかな?識者の意見求む。

常駐している Erlang プロセスのコードの入れ替えに成功する例

失敗する例のテストモジュールから、入れ替えの対象となる get_message/1 を切り離す。

loop() ->
  receive ReceiveMessage -> ok end,
  io:fwrite("~p~n", [test2:get_message(ReceiveMessage)]),
  loop().
-module(test2).

-export([get_message/1]).

get_message(hello) -> hi;
get_message(Other) -> Other.

ErlangVM を起動して動作確認を行なう。

% erl
1> Pid = test:start().
<0.33.0>
2> test:talk(Pid).
hi
ok

この状態で、test2:get_message/1 を下記のように書き換える。

get_message(hello) -> hello;
get_message(Other) -> Other.

ErlangVM でコードの入れ替えを試してみる。

3> l(test2).
{module,test2}
4> test:talk(Pid).
hello
ok

無事、入れ替えに成功。test2 は、常駐しているプロセスとは無関係である為、何度ロードし直してもプロセスが落ちる事はない。

まとめ

  • 常駐している Erlang プロセスのコードは、入れ替え出来ない。
  • 常駐している Erlang プロセスのコードが含まれるモジュールを入れ替えると、プロセスが死ぬ事がある。(深追いしていない。code_server の Source を読めば解る?)
  • 常駐している Erlang プロセスのコードが依存しているモジュールのコードは入れ替え出来る。

OTP に従っていれば、プロセスを作成・常駐する箇所は共通モジュール化されている為、一般化できないアプリケーション固有の処理は、別モジュールに自然と切り出される。未熟だと自覚している者こそ OTP に従って間違いの無いプログラムを書きましょう。という事かな。