Skip to content

Instantly share code, notes, and snippets.

@sile
Last active June 30, 2016 16:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save sile/f6766dc49398e9a7f49a to your computer and use it in GitHub Desktop.
Save sile/f6766dc49398e9a7f49a to your computer and use it in GitHub Desktop.
ホットコードローディング時のプロセスクラッシュについて ref: http://qiita.com/sile/items/697f80db992819056127
-module(hoge).
-export([gen_external_fun/0, gen_local_fun/0, gen_anonymouns_fun/0]).
-export([hello/0]).
gen_external_fun() -> % 公開関数への参照を返す
fun hoge:hello/0. % これだけが完全修飾形式での参照 (= 複数回のコードローディングを跨いでも安全)
gen_local_fun() -> % ローカル関数への参照を返す
fun hello/0.
gen_anonymouns_fun() -> % 無名関数への参照を返す
fun () -> hoge:hello() end.
hello() ->
?HELLO. % マクロの値はコンパイル時に指定する
> c(hoge, [{d, 'HELLO', hello}]). % コンパイル
> (hoge:gen_external_fun())().
hello
> (hoge:gen_local_fun())().
hello
(hoge:gen_anonymouns_fun())().
hello
%% 1. 'fun Module:Function/Arity'形式の場合
> erlang:fun_info(hoge:gen_external_fun()).
[{module,hoge}, % erlang:fun_info/1で返される情報は、ほとんど見た目上のそれと同じ
{name,hello},
{arity,0},
{env,[]},
{type,external}] % type=external
%% 2. 'fun Functoin/Arity'形式の場合
> erlang:fun_info(hoge:gen_local_fun()). % 返される情報が前者に比べてだいぶ増えている
[{pid,<0.47.0>},
{module,hoge}, % 上の関数は、このモジュールの現在のインスタンスを参照している
{new_index,0}, % 参照先モジュールインスタンスの関数テーブル内でのインデックス
{new_uniq,<<32,234,39,54,23,31,234,108,60,180,205,165,64,
8,222,180>>}, % モージュールインスタンスを識別するID (大雑把にいえば)
{index,0},
{uniq,17256761},
{name,'-gen_local_fun/0-fun-0-'},
{arity,0},
{env,[]},
{type,local}] % type=local
%% 3. 'fun (Args) -> Body end'形式の場合
> erlang:fun_info(hoge:gen_anonymouns_fun()).
[{pid,<0.47.0>},
{module,hoge}, % 以下の三つの値の意味は、一つ上の場合と同様
{new_index,1}, % 関数テーブル内でのインデックスが一つ増えている
{new_uniq,<<32,234,39,54,23,31,234,108,60,180,205,165,64,
8,222,180>>},
{index,1},
{uniq,17256761},
{name,'-gen_anonymouns_fun/0-fun-0-'},
{arity,0},
{env,[]},
{type,local}] % type=local
> HogeFun = fun hogefuga:piyo/0. % 存在しないモジュールの存在しない関数への参照を生成
#Fun<hogefuga.piyo.0>
> HogeFun(). % 実際の呼び出し時に、実体を解決出来ずにエラーとなる
** exception error: undefined function hogefuga:piyo/0
%% 1. 完全修飾形式での参照の場合
% まだoldバージョンは存在しない
> erlang:check_old_code(hoge).
false
% 自プロセスがold版を実行していないか確認
> erlang:check_process_code(self(), hoge).
false % oldのhogeは使っていない
% 完全修飾形式での参照を保持してみる
> Fun1 = hoge:gen_external_fun().
% 中身を変更してモジュールの再コンパイル&ロード
> c(hoge, [{d, 'HELLO', hello_world}]).
> erlang:check_old_code(hoge).
true % old版が存在するようになった
> erlang:check_process_code(self(), hoge).
false % 自プロセスはまだold版への参照を保持していない
% 二回以上のロードを跨いても特に問題なし
> c(hoge, [{d, 'HELLO', hello}]).
%% 2. ローカル形式での参照の場合 (無名関数の場合も同様なのでそちらは省略)
% ローカル形式での関数への参照を取得
> Fun2 = hoge:gen_local_fun().
> erlang:check_process_code(self(), hoge).
false % まだold版モジュールインスタンスへの参照は存在しない
% 再コンパイル&ロード
> c(hoge, [{d, 'HELLO', hello_world}]).
> erlang:check_process_code(self(), hoge).
true % old版への参照発生! (Fun2が古いモジュールインスタンスへの参照を保持しているため)
% 再コンパイル&ロード
> c(hoge, [{d, 'HELLO', hello}]).
*** ERROR: Shell process terminated! *** % 二回のコード更新を跨げずにプロセスクラッシュ
-module(fuga).
-export([spawn_external_loop/0, spawn_local_loop/0, spawn_anonymous_external_loop/0]).
-export([external_loop/0, local_loop/0]). % プロセスのメインループ用
spawn_external_loop() -> % external_loop/0を実行するプロセスを起動
%% NOTE: ループの度に、その時点での最新のモジュールインスタンスのコードが実行される
spawn_monitor(?MODULE, external_loop, []).
spawn_local_loop() -> % local_loop/0を実行するプロセスを起動
%% NOTE: 参照(実行)するモジュールインスタンスは、この時点で固定化される
spawn_monitor(?MODULE, local_loop, []).
spawn_anonymous_external_loop() -> % 無名関数経由でexternal_loop/0を実行するプロセスを起動
%% NOTE:
%% - 無名関数自体は特定のモジュールインスタンスを参照する
%% - ただし、起動後にすぐに external_loop/0 に処理が移り、無名関数への参照は捨てられるので、
%% 実質的には spawn_external_loop/0 とほぼ同様の挙動となる
spawn_monitor(fun () -> ?MODULE:external_loop() end).
external_loop() ->
timer:sleep(?WAIT),
?MODULE:external_loop(). % 完全修飾形式で次のループを呼び出す (コード更新があれば最新のものが使用される)
local_loop() ->
timer:sleep(?WAIT),
local_loop(). % ローカル呼び出し (コード更新があっても現在の実行中のバージョンを使い続ける)
%% コンパイル
> c(fuga, [{d, 'WAIT', 1}]).
%% プロセス起動
2> fuga:spawn_external_loop().
{<0.40.0>,#Ref<0.0.0.78>}
3> fuga:spawn_local_loop(). % このプロセスだけローカル呼び出しでループしている
{<0.42.0>,#Ref<0.0.0.83>}
4> fuga:spawn_anonymous_external_loop().
{<0.44.0>,#Ref<0.0.0.88>}
%% oldを参照しているプロセスがないかの確認: 現時点では全部currentのみを参照している
5> erlang:check_process_code(pid(0,40,0), fuga).
false
6> erlang:check_process_code(pid(0,42,0), fuga).
false
7> erlang:check_process_code(pid(0,44,0), fuga).
false
%% コード更新: 一回目
> c(fuga, [{d, 'WAIT', 10}]).
%% old確認
9> erlang:check_process_code(pid(0,40,0), fuga).
false
10> erlang:check_process_code(pid(0,42,0), fuga). % fuga:spawn_local_loop/0だけがoldへの参照を保持
true
11> erlang:check_process_code(pid(0,44,0), fuga).
false
%% コード更新: 二回目
c(fuga, [{d, 'WAIT', 1}]).
> flush(). % fuga:spawn_local_loop/0 で起動したプロセスのダウン通知メッセージが届いている
Shell got {'DOWN',#Ref<0.0.0.83>,process,<0.42.0>,killed}
ok
> erlang:is_process_alive(pid(0,40,0)).
true
> erlang:is_process_alive(pid(0,42,0)).
false % down
> erlang:is_process_alive(pid(0,44,0)).
true
> List = [1,2,3].
> UnsafeFun = fun () -> lists:length(List) end. % 無名関数なのでモジュールインスタンスへの参照が発生する
> SafeFun = {fun lists:length/1, [List]}. % 完全修飾参照と追加引数をタプルで別々に保持すればOK
> my_apply(UnsafeFun, []).
3.
> my_apply(SafeFun, []).
3.
%% my_apply/2の定義
my_apply({Fun, ExtraArgs}, Args) -> apply(Fun, ExtraArgs++Args);
my_apply(Fun) , Args) -> apply(Fun, Args).
-module(piyo).
-export([wait_loop/0, recv_loop/0]).
wait_loop() ->
%% 一時間スリープしてから、次のループを実行する
%% => 一時間(sleep)の間に二回このモジュールの更新が行われた場合は、プロセスクラッシュ
timer:sleep(60 * 60 * 1000),
?MODULE:wait_loop().
recv_loop() ->
receive
Msg ->
do_something(Msg), % 何らかの処理を実行
%% メッセージを受信し、それに対応する処理が完了したら、次のループを実行する
%% => 受信を挟まずに二回このモジュールの更新が行われた場合は、プロセスクラッシュ
?MODULE:recv_loop()
end.
pfun:lambda(fun () -> hello end, []). % pfun:lambda/2で無名関数を囲む
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment