練習用コード。1つのプロセスで1件の予定を記憶する、すげーシンプルなリマインダ。
最初のバージョン(もちろん間違っている)
-module(sched).
-compile(export_all).
start() ->
spawn(fun() -> calender(self()) end).
rpc(Pid, Request) ->
Pid ! {self(), Request},
receive
{Pid, Response} ->
Response
after 10000 ->
response_wait_timeout
end.
calender(Pid) ->
receive
{Pid, {put, Until, Task}} ->
TaskPid = spawn(fun() -> waituntil(Pid, Until, Task) end),
io:format("received",),
Pid ! {Pid, {TaskPid, ok}},
calender(Pid);
{Pid, {cancel, TaskPid}} ->
TaskPid ! {Pid , cancel},
io:format("cancel kickked: ~p~n",[TaskPid]),
Pid ! {Pid, cancel_done},
calender(Pid);
_Any ->
io:format("dropped : ~p~n",[_Any]),
Pid ! {Pid, mnn}
end.
waituntil(Pid, Until, Task) ->
receive
{KillerPid, cancel} ->
io:format("killed",),
KillerPid! {KillerPid, "I am dropped"}
after Until ->
io:format("DONE",[]),
Pid ! {Pid, Task}
end.
デバッグプリントごりごりで、とにかくcancelできましたってやつ
-module(sched1). -compile(export_all). start() -> ParentPid = self(), spawn(fun() -> calender(ParentPid) end). rpc(TargetPid, Request) -> io:format("rpc:~p: kick to ~p, by ~p~n",[self(),TargetPid, Request]), ReturnPid = self(), TargetPid ! {ReturnPid, Request}, receive {ReturnPid, Response} -> io:format("rpc:~p: response received normal response ~p~n",[self(),Response]), Response; _Any -> io:format("rpc:~p: response received invalid response ~p~n",[self(),_Any]), _Any after 10000 -> response_wait_timeout end. set_timer(Timeout, Task) -> CalenderPid = start(), rpc(CalenderPid, {put, Timeout, Task}). cancel_timer(SchedulePid) -> rpc(SchedulePid, {cancel, self()}). check_timer() -> WaitPid = self(), receive {WaitPid, Result} -> io:format("check_timer:~p: Result: ~p~n",[self(),Result]) after 0 -> "not yet." end. calender(Pid) -> io:format("calender:~p: caller Pid is set as : ~p~n",[self(),Pid]), receive {Pid, {put, Until, Task}} -> io:format("calender:~p: put cmd kicked from ~p until: ~p task: ~p~n",[self(),Pid, Until, Task]), TaskPid = spawn(fun() -> waituntil(Pid, Until, Task) end), io:format("calender:~p: task pid: ~p spawned~n",[self(),TaskPid]), Pid ! {Pid, {TaskPid, ok}}, calender(Pid); {Pid, {cancel, TaskPid}} -> TaskPid ! {Pid , cancel}, io:format("calender:~p: cancel kicked from: ~p for task pid: ~p~n",[self(),Pid, TaskPid]), Pid ! {Pid, cancel_done}, calender(Pid); {_T,_Any} -> io:format("calender:~p: dropped message from: ~p, should be: ~p, remainings: ~p~n",[self(),_T,Pid,_Any]), Pid ! {Pid, mnn} end. waituntil(Pid, Until, Task) -> receive {KillerPid, {cancel, KillerPid}} -> io:format("waituntil:~p: killed~n",[self()]), KillerPid! {KillerPid, "I am dropped"}; _Any -> io:format("waituntil:~p: invalid command ~p~n",[self(),_Any]), waituntil(Pid, Until, Task) after Until -> io:format("waituntil:~p:DONE set timer from ~p of task : ~p~n",[self(), Pid, Task]), Pid ! {Pid, Task} end.
実行結果サンプル1(かろうじてキャンセルはできた)
1> c(sched1). {ok,sched1} 2> R=sched1:set_timer(60000, "wait 1 minute"). rpc:<0.31.0>: kick to <0.38.0>, by {put,60000,"wait 1 minute"} calender:<0.38.0>: caller Pid is set as : <0.31.0> calender:<0.38.0>: put cmd kicked from <0.31.0> until: 60000 task: "wait 1 minute" calender:<0.38.0>: task pid: <0.39.0> spawned calender:<0.38.0>: caller Pid is set as : <0.31.0> rpc:<0.31.0>: response received normal response {<0.39.0>,ok} {<0.39.0>,ok} 3> {S, ok} = R. {<0.39.0>,ok} 4> S. <0.39.0> 5> sched1:cancel_timer(S). rpc:<0.31.0>: kick to <0.39.0>, by {cancel,<0.31.0>} waituntil:<0.39.0>: killed rpc:<0.31.0>: response received normal response "I am dropped" "I am dropped"
実行結果サンプル2:タイムアウトしたケース(あ、メッセージ拾ってないな)
6> T=sched1:set_timer(600, "wait 0.6 sec"). rpc:<0.31.0>: kick to <0.44.0>, by {put,600,"wait 0.6 sec"} calender:<0.44.0>: caller Pid is set as : <0.31.0> calender:<0.44.0>: put cmd kicked from <0.31.0> until: 600 task: "wait 0.6 sec" calender:<0.44.0>: task pid: <0.45.0> spawned calender:<0.44.0>: caller Pid is set as : <0.31.0> rpc:<0.31.0>: response received normal response {<0.45.0>,ok} {<0.45.0>,ok} waituntil:<0.45.0>:DONE set timer from <0.31.0> of task : "wait 0.6 sec"
ところで、これは練習というか、素振りレベルなんで、これ以上は深追いしない(汗
デバッグしてて感じたのは、コードのスタイルというか、rpc関数ではself()を最初に埋めておく、というbest practiceみたいなのが身に付くと、かなり楽だなということ。
これが不統一なコードを書いてしまったところで激ハマった。
現状のコードは無駄が多いので、これは捨てるけれど、もう一度書くなら、rpcと対になるdebug_trace_printみたいな関数が書きやすいように意識してメッセージのスタイルを決めるだろう。
もひとつの練習
reverse proxy。タイムアウトしたら、ごめんなさいページを表示するってやつ。書いておくこと>自分
これが作れて、オーバーヘッドがかなり小さければ、DoSアタックやYahooとかからのリンクを受けたときのピークパフォーマンス(受け入れ可能な同時接続数)が予見可能かつかなり多くなるし、ごめんなさいページの仕様を実行中に書き換えたりもできる。