BashoのrebarというErlang用ビルドツール(?)を使い始めて3週間ほどたったのですが、ようやく「複数階層」「複数スーパバイザ」という今回のSimple dungeonの構成を模したソースツリーが構築できました。
なんというか、この感謝を言葉にするのがすごく難しい。rebarを作って公開してくれたBashoのみなさん、あと、サイトでrebarを紹介されてたVoluntasさんにも。
rebar入れてから「とりあえず使えるようになりました」と言えるまで3週間も時間がかかったのは、今まで、まじめにOTPを使ってこなかったのが原因です。ずっとErlangとしてしか使ってなかった。OTPじゃなかった。rebarはもう初日からバリバリとテンプレートコードを生成してくれてたのに、もうほんと申し訳ないって感じ。rebarが吐き出してくれるコードが意味わかんない状態だったからもうw
ただ、rebarのありがたみというかOTPの有り難みは、「自作loopでメッセージを何とかこなすのが精一杯の、それでも目的の動作はしてくれる初期構築コード」を経たからなのかもしれない。それでも4年はかけ過ぎだけどww (今確認したら、アマゾンから飛行機本を買ったのが2008年3月21日。うは。まじ4年)
今、とりあえず、OTPベースのプロセス監視ツリーをきちんと構築したsimpledungeonにリフレッシュ作業中です。そこまでできたら、コードの動的入れ替えやら、いろんなことが可能になって、ちゃんと「リリースしました」といえる気がする。今はまだ「落ちたらごめんなさいサンプル」だ。それではオンラインゲームのサーバーとしては「実証段階」だし、そんなレベルじゃ誰も使わない(私も使わない)。
というわけで、落ちないsimpledungeonに向けて、コードの書き換え中。rebarベースに移植が完了したら、commitしてまたご報告します。
とにかく感謝の気持ちがいっぱいなので、とりあえずエントリ書きました。
ありがとーーー!
2012年03月30日
2012年03月11日
Windowsでrebarを使う
ふだんの開発をWindowsで行っているのですが、ErlangのビルドツールとしてVoluntasさんがこちらの記事で推奨してた、rebar (githubはこちら、zipはこちら)を入れてみることにしました。
Windows固有のハマリが2つ。
・nmakeでmakeできない。
・self containing escriptになってるのを動かそうとしたら、escriptが下のようなメッセージを出してrebarを実行してくれない
というわけで、それぞれのとりあえずの解決。
・nmakeでmakeできない。
本体のビルド作業だけなら bootstrap.bat を実行すればよいので、makeではなく
のようにすればOKです。
・escriptがエラーを出してrebarを実行してくれない
上の手順でビルドして作られた「rebar」ファイルだけを持ってくれば動くはずなのですが、今度はescriptがうまく動かない。。。
解決してしまえばシンプルでした。
Franco Lombardoさんのこちらのエントリ(英語)にあるように、escriptを"escript.exe" として起動してあげないと、引数のファイルを読みに行ってくれないようです。
escriptのソースは読んでませんが、argv[0]が自分のファイル名(escript.exe)でない場合は、escript.exeが「argv[1]ではなく、argv[0]が実行すべきescriptファイルだ」と判断して、argv[0]そのもの、もしくはargv[0]に".escript"を付加したファイルを探しに行く、という動作になっているものと思われます。これ自体は妥当な動きなので「escript.exe」で実行するようにすればいいだけですね。
というわけで、
という流れで、Windowsでもrebarの準備ができました^^
※補足
ビルドされたrebarファイルですが、Windowsで作ってもLinuxで作っても同じようにUnixのスクリプトファイル形式(#!で始まるあれ)で出力されます。というわけで、Erlangのバージョンが同じならLinux環境でビルドしたものをそのまま使うことも可能ですね。
Windows固有のハマリが2つ。
・nmakeでmakeできない。
・self containing escriptになってるのを動かそうとしたら、escriptが下のようなメッセージを出してrebarを実行してくれない
C:\HOGEHOGE>escript rebar
escript: Failed to open file: C:\HOGEHOGE\escript.escript
というわけで、それぞれのとりあえずの解決。
・nmakeでmakeできない。
本体のビルド作業だけなら bootstrap.bat を実行すればよいので、makeではなく
C:\rebar> bootstrap.bat
のようにすればOKです。
・escriptがエラーを出してrebarを実行してくれない
上の手順でビルドして作られた「rebar」ファイルだけを持ってくれば動くはずなのですが、今度はescriptがうまく動かない。。。
解決してしまえばシンプルでした。
動かない > C:\HOGEHOGE> escript rebar
動く! > C:\HOGEHOGE> escript.exe rebar
Franco Lombardoさんのこちらのエントリ(英語)にあるように、escriptを"escript.exe" として起動してあげないと、引数のファイルを読みに行ってくれないようです。
escriptのソースは読んでませんが、argv[0]が自分のファイル名(escript.exe)でない場合は、escript.exeが「argv[1]ではなく、argv[0]が実行すべきescriptファイルだ」と判断して、argv[0]そのもの、もしくはargv[0]に".escript"を付加したファイルを探しに行く、という動作になっているものと思われます。これ自体は妥当な動きなので「escript.exe」で実行するようにすればいいだけですね。
というわけで、
C:\> copy \rebar\rebar \HOGEHOGE
C:\> cd \HOGEHOGE
C:\> escript.exe rebar
という流れで、Windowsでもrebarの準備ができました^^
※補足
ビルドされたrebarファイルですが、Windowsで作ってもLinuxで作っても同じようにUnixのスクリプトファイル形式(#!で始まるあれ)で出力されます。というわけで、Erlangのバージョンが同じならLinux環境でビルドしたものをそのまま使うことも可能ですね。
2012年03月06日
Simple dungeonのビルドがGreenに戻りました
というわけで、開発中のサーバー側ソフトSimple dungeonのビルドがGreenに戻りました。
Jenkinsのページはこちら。
マルチテナント化するために直し壊ししてましたが、ようやくスタート地点まで押し戻したという感じです。
リファクタリングとかはこれからですが、なんとか開発を加速していきたいですね。
Jenkinsのページはこちら。
マルチテナント化するために直し壊ししてましたが、ようやくスタート地点まで押し戻したという感じです。
リファクタリングとかはこれからですが、なんとか開発を加速していきたいですね。
2012年02月29日
Simple dungeon Rev.252、エラー中です
開発中のMMO作成支援サービス「Simple dungeon」で、trunkのツリーにエラーが発生中です。
Simple dungeonのJenkins画面
branch側で試していた新しい構成を、先週からtrunkに統合しているのですが、一部モジュール名やデータの持ち方を変えようとしているためエラーが出ています。
モジュール名を変えたり、ユーティリティ関数を別モジュールに移したりという変更もしているので、テストが通らないだけでなく、動作にも支障があります。
現在、修正中です。
補足。チェックインしなければいいのに? おっしゃるとおりです。。。
Simple dungeonのJenkins画面
branch側で試していた新しい構成を、先週からtrunkに統合しているのですが、一部モジュール名やデータの持ち方を変えようとしているためエラーが出ています。
モジュール名を変えたり、ユーティリティ関数を別モジュールに移したりという変更もしているので、テストが通らないだけでなく、動作にも支障があります。
現在、修正中です。
補足。チェックインしなければいいのに? おっしゃるとおりです。。。
2012年02月25日
gen_server内のハンドルでio:formatで出力するとエラーになる
作成中のSimpledungeon、Erlang実行環境ををErlang/OTP R15Bにアップしたところ、R13B04でノーエラー状態だったはずのtrunkのコードがテストでエラー出まくりになっていました。
で、ずっと追っかけてたのですが、どうやらgen_server内のハンドルでio:formatで出力するとエラーになるように修正が加えられたようです(リリースノートの追跡は後でやります(やらないフラグ))
というわけで、各所を修正しました。
sourceforgeに置いたsimple dungeon はこちら。
で、ずっと追っかけてたのですが、どうやらgen_server内のハンドルでio:formatで出力するとエラーになるように修正が加えられたようです(リリースノートの追跡は後でやります(やらないフラグ))
というわけで、各所を修正しました。
sourceforgeに置いたsimple dungeon はこちら。
2012年01月09日
cryptoのベンチマークを追試した
ゆうべ、cryptoがボトルネックになりうるかどうかを計測してみましたが、今朝起きて「さすがにバラつきすぎだろう」と思い、同じコードをLinuxにもっていって計測し直してみました。
同じマシンで、Windows版VirtualboxでCentOS6.2 32bit版を動かしています。
10回計測、単位は秒。
というわけで、ばらつきが減って、cryptoの負荷がどれくらいかが見えやすくなりましたね。
とした結論のところは変わりません。
同じマシンで、Windows版VirtualboxでCentOS6.2 32bit版を動かしています。
10回計測、単位は秒。
1万プロセス | 10万プロセス | |||
暗号なし | 暗号あり | 暗号なし | 暗号あり | |
平均 | 0.105 | 0.113 | 7.730 | 7.806 |
最大 | 0.189 | 0.216 | 7.852 | 7.886 |
最小 | 0.093 | 0.098 | 7.694 | 7.789 |
というわけで、ばらつきが減って、cryptoの負荷がどれくらいかが見えやすくなりましたね。
cryptoへ一気に流し込んでも、それほど処理の影響はなさそうです。
そもそもcryptoの処理自体が(全体と比して)軽そうです。
とした結論のところは変わりません。
cryptoの処理能力を試してみた>想定用途には余裕っぽい
というわけで、暗号化ライブラリ「crypto」の使いどころのエントリで、voluntasさんから秒間数千程度なら「気にせず使っても問題ありませんよ」とのコメントをいただいてたので、ちょっとコードを書いて試してみました。
Windows7 64ビット版
Erlang OTP R15B(x64)
CPU AMD PhenomII X4 905e 2.5GHz
メモリ 12GB
という環境で、erl.exeにて実行。10回の計測を実施、単位は秒。
でした。割とばらつくのはWindows上のVMだからでしょうか。1万プロセス〜10万プロセスぐらいだと最大も最小もほぼ変わらないですね。cryptoへ一気に流し込んでも、それほど処理の影響はなさそうです。
そもそもcryptoの処理自体が(全体と比して)軽そうです。
crypto処理ありのベンチマークテストコード。
こちらがcrypto処理無しの方。
Windows7 64ビット版
Erlang OTP R15B(x64)
CPU AMD PhenomII X4 905e 2.5GHz
メモリ 12GB
という環境で、erl.exeにて実行。10回の計測を実施、単位は秒。
1万プロセス | 10万プロセス | |||
暗号なし | 暗号あり | 暗号なし | 暗号あり | |
平均 | 0.304 | 0.278 | 38.286 | 40.150 |
最大 | 0.499 | 0.500 | 104.365 | 117.983 |
最小 | 0.156 | 0.156 | 10.452 | 10.936 |
でした。割とばらつくのはWindows上のVMだからでしょうか。1万プロセス〜10万プロセスぐらいだと最大も最小もほぼ変わらないですね。cryptoへ一気に流し込んでも、それほど処理の影響はなさそうです。
そもそもcryptoの処理自体が(全体と比して)軽そうです。
crypto処理ありのベンチマークテストコード。
-module(cryptobench).
-import(lists, [foreach/2]).
-compile(export_all).
start(A) ->
crypto:start(),
E = encrypt_text("Hello World!"),
L = lists:seq(1, A, 1),
SendBackPid = self(),
StartTime = erlang:now(),
lists:map(fun(X) -> spawn(fun() -> sendback(SendBackPid, X, E) end) end ,L),
loop([], A),
EndTime = erlang:now(),
Diff = timer:now_diff( EndTime, StartTime ),
io:format("elapsed time: ~p (sec)~n", [ Diff / 1000000 ]),
crypto:stop().
loop(State, Count) when length(State) == Count ->
io:format("end of loop~p~n", [length(State)]);
loop(State, Count) ->
receive
{hello, Id, "Hello World!", _SenderPid} -> loop([Id | State], Count);
_ -> loop(State, Count)
end.
sendback(To, Id, E) ->
To ! {hello, Id, decrypt_text(E), self()}.
%% Encryption utilities.
encryption_key() -> <<16#01,16#23,16#45,16#67,16#89,16#AB,16#CD,16#EF,16#F0,16#12,16#34,16#56,16#78,16#9A,16#BC,16#DE>>.
encryption_iv() -> <<16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00>>.
encrypt_text(PlainText) ->
encrypt_bin(erlang:list_to_binary(PlainText)).
encrypt_bin(Binary) ->
Key = encryption_key(),
IVec = crypto:aes_cbc_ivec(encryption_iv()),
crypto:aes_ctr_encrypt(Key, IVec, Binary).
decrypt_text(Encrypted) ->
erlang:binary_to_list(decrypt_bin(Encrypted)).
decrypt_bin(Encrypted) ->
Key = encryption_key(),
IVec = crypto:aes_cbc_ivec(encryption_iv()),
crypto:aes_ctr_decrypt(Key, IVec, Encrypted).
こちらがcrypto処理無しの方。
-module(bench).
-import(lists, [foreach/2]).
-compile(export_all).
start(A) ->
E = "Hello World!",
L = lists:seq(1, A, 1),
SendBackPid = self(),
StartTime = erlang:now(),
lists:map(fun(X) -> spawn(fun() -> sendback(SendBackPid, X, E) end) end ,L),
loop([], A),
EndTime = erlang:now(),
Diff = timer:now_diff( EndTime, StartTime ),
io:format("elapsed time: ~p (sec)~n", [ Diff / 1000000 ]).
loop(State, Count) when length(State) == Count ->
io:format("end of loop~p~n", [length(State)]);
loop(State, Count) ->
receive
{hello, Id, "Hello World!", _SenderPid} -> loop([Id | State], Count);
_ -> loop(State, Count)
end.
sendback(To, Id, E) ->
To ! {hello, Id, E, self()}.
2012年01月07日
mnesia:transaction/1から、mnesia:activity/2への変更
mnesia:transaction/1について、いろいろ書いてたところ、voluntas さんから「mnesia:transaction/1 ではなく mnesia:activity/2 を使うといいですよ。」とのアドバイスをいただいた。
mnesia:activity/2は飛行機本で紹介されてなかったこともあり、まったく知らなかったので、ドキュメントとかvoluntasさんの書かれたソースとか見てみた。とりあえず、mnesia:transaction/1に近い使い方をしたいだけなら、ちょっと書き換えるだけで使えそうな気配。具体的にはこんな感じ。
mnesia:activity/2にするときは、
・第1引数に「transaction」を指定する。
・返ってくる値は「 {atomic, VALUE} 」ではなく、「 VALUE 」となる。
の2点でなんとかなりそう。
トランザクション以外の処理させ方もいろいろ使えるみたい。ドキュメントはこちら>mnesia:activity/2
ちなみに、存在しないテーブル名を指定するなどして失敗すると、mnesia:transaction/1だったら{aborted, Reason}だったのが、mnesia:activity/2だと、いきなりexitしてくるんですね。気をつけます。
mnesia:activity/2は飛行機本で紹介されてなかったこともあり、まったく知らなかったので、ドキュメントとかvoluntasさんの書かれたソースとか見てみた。とりあえず、mnesia:transaction/1に近い使い方をしたいだけなら、ちょっと書き換えるだけで使えそうな気配。具体的にはこんな感じ。
1> mnesia:transaction(fun() -> mnesia:read({sample, foo}) end).
{atomic,[{sample, foo, data_of_foo}]}
2> mnesia:activity(transaction, fun() -> mnesia:read({sample, foo}) end).
[{sample, foo, data_of_foo}]
mnesia:activity/2にするときは、
・第1引数に「transaction」を指定する。
・返ってくる値は「 {atomic, VALUE} 」ではなく、「 VALUE 」となる。
の2点でなんとかなりそう。
トランザクション以外の処理させ方もいろいろ使えるみたい。ドキュメントはこちら>mnesia:activity/2
ちなみに、存在しないテーブル名を指定するなどして失敗すると、mnesia:transaction/1だったら{aborted, Reason}だったのが、mnesia:activity/2だと、いきなりexitしてくるんですね。気をつけます。
2012年01月06日
暗号化ライブラリ「crypto」の使いどころ
ErlangでAESとかの暗号化を行いたいときに使う「crypto」ですが、OpenSSLのライブラリ(シェアードオブジェクトやDLL)をPort経由で使ってるんですね。
Simple dungeonで、Mnesiaに保存する際にパスワードや連絡先メールアドレスなどを暗号化しようとしているのですが、この構成だと、多数のErlangプロセスが好き放題呼ぶと、ここが隘路になるので、使いどころに注意が必要な感じです。
まあ、ちょっとやそっとじゃここがボトルネックになるほどの負荷はかからないと思うのですが、いったんボトルネック判定されたら、そこからの対策が難しいので、たまーに参照するぐらいの頻度にしておきます。。。
Simple dungeonで、Mnesiaに保存する際にパスワードや連絡先メールアドレスなどを暗号化しようとしているのですが、この構成だと、多数のErlangプロセスが好き放題呼ぶと、ここが隘路になるので、使いどころに注意が必要な感じです。
まあ、ちょっとやそっとじゃここがボトルネックになるほどの負荷はかからないと思うのですが、いったんボトルネック判定されたら、そこからの対策が難しいので、たまーに参照するぐらいの頻度にしておきます。。。
Erlangアプリケーションの「設定ファイル」をどう読みこませるか
Simple dungeonで、Mnesiaのノードリストや、移動処理でのインターバル値(1ブロック歩くのにかかる秒数)などをカスタマイズできるようにしたいなと思って、「設定ファイル」をどう作ればいいか調べました。
YAMLとか.ini、.confファイルのような、インストールしたアプリケーションの動作をカスタマイズするためのファイルフォーマットがあるかと思ったら、Erlangには file:consult/1 という関数が用意されていて、Erlang termで書いておけばそのままアトムや文字列、数値、リストなどを読み込んでくれるんですね。これは便利。
XMLで作って読ませることも考えたのですが、今のところはそこに注力する必要はないので、素直にfile:consult/1を使うかたちで行くことにします。
YAMLとか.ini、.confファイルのような、インストールしたアプリケーションの動作をカスタマイズするためのファイルフォーマットがあるかと思ったら、Erlangには file:consult/1 という関数が用意されていて、Erlang termで書いておけばそのままアトムや文字列、数値、リストなどを読み込んでくれるんですね。これは便利。
XMLで作って読ませることも考えたのですが、今のところはそこに注力する必要はないので、素直にfile:consult/1を使うかたちで行くことにします。
2011年07月20日
mnesia:transaction/1固有の返値を
Simple dungeonはぼちぼちと更新中。
ちょっと迷ってるのが、各種データの保存/読み込み処理の、成功/失敗判断の条件。
今のコードはデータの保存にmnesiaを使っています。
したがって、mmoasp:change_passwordのような関数はすべてmnesia:transactionを中で呼んでます。返値も、mnesia:transactionの結果をほぼ素で返したいところ。
mnesia:transaction/1は、成功すると{atomic, _}、失敗すると{ng, _}を返すわけですが、呼び出し側で、「{atomic, ok}が帰ってきたら書き込み成功」と書いてしまっていいものかどうか。
「mnesiaを隠蔽した方がいいのではないか」という気もしつつ、「どうせ当面はmnesiaでいくんだし」という気もする。
YAGNIの原則からすると、「mnesia以外のストレージへの切り替え機能をつけるまでは、素直に{atomic,ok}で書いておけ」という気もするのですが、Erlang初学者としては、ここの「{atomic」を上位コードのcaseとかwhenにバキバキ書くというのが、どうにも気持ちが悪い。
他の人の書いたコードを読むか、Erlang MLに質問投げてみるか、、、
ちょっと迷ってるのが、各種データの保存/読み込み処理の、成功/失敗判断の条件。
今のコードはデータの保存にmnesiaを使っています。
したがって、mmoasp:change_passwordのような関数はすべてmnesia:transactionを中で呼んでます。返値も、mnesia:transactionの結果をほぼ素で返したいところ。
mnesia:transaction/1は、成功すると{atomic, _}、失敗すると{ng, _}を返すわけですが、呼び出し側で、「{atomic, ok}が帰ってきたら書き込み成功」と書いてしまっていいものかどうか。
「mnesiaを隠蔽した方がいいのではないか」という気もしつつ、「どうせ当面はmnesiaでいくんだし」という気もする。
YAGNIの原則からすると、「mnesia以外のストレージへの切り替え機能をつけるまでは、素直に{atomic,ok}で書いておけ」という気もするのですが、Erlang初学者としては、ここの「{atomic」を上位コードのcaseとかwhenにバキバキ書くというのが、どうにも気持ちが悪い。
他の人の書いたコードを読むか、Erlang MLに質問投げてみるか、、、
2011年04月18日
erlシェルからの起動と、-detached指定でデーモン起動したときの動作が違う(デタッチするとサービスが落ちる)
Simpledungeonをスーパバイザ挙動を使って動かす作業中です。
erlシェルからの起動と、-detached指定でデーモン起動したときの動作が違う(デタッチするとサービスが落ちる)という現象に出くわしました。
コマンドラインで
erl -pa /opt/yaws/lib/yaws/ebin
と起動して、erlang shellから
simpledungeon:start().
と入力すると、サービスはちゃんと動く。
でも、同じバイナリで
erl -pa /opt/yaws/lib/yaws/ebin -noinput -noshell -run simpledungeon start -detached &
というふうに、デーモン起動すると、なぜかサービスが動いてないわけです。
いろいろ見て回って、最終的に檜山さんのまとめた"helloちゃんと作るシリーズ"の記述に答えがありました。
http://erlang.g.hatena.ne.jp/m-hiyama/20090114/1231891538
ここ。
要するに、デーモン起動したときだけサービスが動かないというのは、start_linkで起動してるのに親シェルがデタッチしたためにサービスが止まった、ということのようです。
で、檜山さんのスケルトンを参考に、unlinkの1行を追加したところ、Erlangシェルでスーパバイザを起動したときと同じように、サービスが動き続けてくれました。
=======================
修正前:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
=======================
修正後:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
unlink(Pid),
=======================
うー、つかれた。
start_linkしてunlinkするんだったら、素直に「start」させてくれよ、という気もしますが、そこはOTPの長年の熟成の中で入ってないわけですから、ひとまずこの作法に従うことにします。まあ、unlinkがいるのは、シェルから起動するアプリケーション最上位のsupervisorだけ、それ以外は全部linkしててほしいので、この構成は正しいと考えるのが妥当でしょうね。
erlシェルからの起動と、-detached指定でデーモン起動したときの動作が違う(デタッチするとサービスが落ちる)という現象に出くわしました。
コマンドラインで
erl -pa /opt/yaws/lib/yaws/ebin
と起動して、erlang shellから
simpledungeon:start().
と入力すると、サービスはちゃんと動く。
でも、同じバイナリで
erl -pa /opt/yaws/lib/yaws/ebin -noinput -noshell -run simpledungeon start -detached &
というふうに、デーモン起動すると、なぜかサービスが動いてないわけです。
いろいろ見て回って、最終的に檜山さんのまとめた"helloちゃんと作るシリーズ"の記述に答えがありました。
http://erlang.g.hatena.ne.jp/m-hiyama/20090114/1231891538
ここ。
要するに、デーモン起動したときだけサービスが動かないというのは、start_linkで起動してるのに親シェルがデタッチしたためにサービスが止まった、ということのようです。
で、檜山さんのスケルトンを参考に、unlinkの1行を追加したところ、Erlangシェルでスーパバイザを起動したときと同じように、サービスが動き続けてくれました。
=======================
修正前:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
=======================
修正後:
{ok, Pid} = supervisor:start_link({local,?MODULE},?MODULE,[]),
unlink(Pid),
=======================
うー、つかれた。
start_linkしてunlinkするんだったら、素直に「start」させてくれよ、という気もしますが、そこはOTPの長年の熟成の中で入ってないわけですから、ひとまずこの作法に従うことにします。まあ、unlinkがいるのは、シェルから起動するアプリケーション最上位のsupervisorだけ、それ以外は全部linkしててほしいので、この構成は正しいと考えるのが妥当でしょうね。
supervisorでハマッた
分かってしまえば、ものすごくシンプルなんだけど、simpledungeon開発中にハマッた件。
Erlang/OTPの、supervisor ビヘイビアで、start_linkに渡すスーパバイザ名と、start_childに渡すスーパバイザ名の書き方に違いがあるということに気づかず、謎のエラーが出続けて1週間ほどタイムロスしてました。
●スーパバイザの起動
supervisor:start_link({local,?MODULE},?MODULE,[])
のように書いたわけです。この記述には問題はありません。
●子プロセスであるyawsを起動するところ
子プロセスの起動側に問題がありました。
誤:[supervisor:start_child({local,?MODULE}, Ch) || Ch <- ChildSpecs]
正:[supervisor:start_child(?MODULE, Ch) || Ch <- ChildSpecs]
ずっと、誤の書き方をしていて、下のようなエラーメッセージが出続けていました。なんでnodedownやねん、と。このエラーメッセージの原因が分からなかったわけです(相対ディレクトリ指定ができないのかなとか、どっかがリストになってないのかな、とか)。でも、全然原因は違ってました。。。
=エラーメッセージ:=============
1> simpledungeon:start().
** exception exit: {{nodedown,simpledungeon},
{gen_server,call,
[{local,simpledungeon},
{start_child,
{yaws_log,
{yaws_log,start_link,[]},
permanent,5000,worker,
[yaws_log]}},
infinity]}}
in function gen_server:call/3
in call from simpledungeon:'-start_yaws/0-lc$^0/1-0-'/1
in call from simpledungeon:start_yaws/0
in call from simpledungeon:start/0
2>
===================
原因はシンプルで、start_childの第1引数には、start_linkのときに指定していた{local, 名称}タプルではなく、名前のアトムを渡さないといけなかったのですね。
http://www.erlang.org/doc/man/supervisor.html
にある、supervisor:start_child/2の定義。
=======================
start_child(SupRef, ChildSpec) -> Result
Types:
SupRef = Name | {Name,Node} | {global,Name} | pid()
Name = Node = atom()
ChildSpec = child_spec() | [term()]
Result = {ok,Child} | {ok,Child,Info} | {error,Error}
Child = pid() | undefined
Info = term()
Error = already_present | {already_started,Child} | term()
=======================
SupRefが、「どのスーパバイザに子プロセスを起動させるか」の参照なわけですが、{global,Name}がある一方で、{local,Name}はなく、そのかわりにNameがあるわけです。
というわけで、スーパバイザとスーパバイズ対象の子プロセスをどちらもローカルで立ち上げるのであれば、start_linkでは{local, Name}を指定し、start_childではNameを渡すという起動方法になる、、、のですよ。
はまった。
Erlang/OTPの、supervisor ビヘイビアで、start_linkに渡すスーパバイザ名と、start_childに渡すスーパバイザ名の書き方に違いがあるということに気づかず、謎のエラーが出続けて1週間ほどタイムロスしてました。
●スーパバイザの起動
supervisor:start_link({local,?MODULE},?MODULE,[])
のように書いたわけです。この記述には問題はありません。
●子プロセスであるyawsを起動するところ
子プロセスの起動側に問題がありました。
誤:[supervisor:start_child({local,?MODULE}, Ch) || Ch <- ChildSpecs]
正:[supervisor:start_child(?MODULE, Ch) || Ch <- ChildSpecs]
ずっと、誤の書き方をしていて、下のようなエラーメッセージが出続けていました。なんでnodedownやねん、と。このエラーメッセージの原因が分からなかったわけです(相対ディレクトリ指定ができないのかなとか、どっかがリストになってないのかな、とか)。でも、全然原因は違ってました。。。
=エラーメッセージ:=============
1> simpledungeon:start().
** exception exit: {{nodedown,simpledungeon},
{gen_server,call,
[{local,simpledungeon},
{start_child,
{yaws_log,
{yaws_log,start_link,[]},
permanent,5000,worker,
[yaws_log]}},
infinity]}}
in function gen_server:call/3
in call from simpledungeon:'-start_yaws/0-lc$^0/1-0-'/1
in call from simpledungeon:start_yaws/0
in call from simpledungeon:start/0
2>
===================
原因はシンプルで、start_childの第1引数には、start_linkのときに指定していた{local, 名称}タプルではなく、名前のアトムを渡さないといけなかったのですね。
http://www.erlang.org/doc/man/supervisor.html
にある、supervisor:start_child/2の定義。
=======================
start_child(SupRef, ChildSpec) -> Result
Types:
SupRef = Name | {Name,Node} | {global,Name} | pid()
Name = Node = atom()
ChildSpec = child_spec() | [term()]
Result = {ok,Child} | {ok,Child,Info} | {error,Error}
Child = pid() | undefined
Info = term()
Error = already_present | {already_started,Child} | term()
=======================
SupRefが、「どのスーパバイザに子プロセスを起動させるか」の参照なわけですが、{global,Name}がある一方で、{local,Name}はなく、そのかわりにNameがあるわけです。
というわけで、スーパバイザとスーパバイズ対象の子プロセスをどちらもローカルで立ち上げるのであれば、start_linkでは{local, Name}を指定し、start_childではNameを渡すという起動方法になる、、、のですよ。
はまった。
2011年04月07日
2011年03月31日
Jenkins-CIのプロジェクト設定はこんなです
Simpledungeonについて稼働中のJenkinsはこちら。
Jenkins-CIとEUnitで自動テストを構築の記事で、Jenkins-CIでErlangのテストを開始したよと書いたのですが、プロジェクト設定の画面があったほうがいいかなと思い、このエントリを書いてます。
antとかでビルドするようですが、よく知らないのでシェルでmakeとmake testを実行してます。最初Trac LightningのHudsonでやってたときは、nmakeとnmake testとしていました。
Jenkins-CIとEUnitで自動テストを構築の記事で、Jenkins-CIでErlangのテストを開始したよと書いたのですが、プロジェクト設定の画面があったほうがいいかなと思い、このエントリを書いてます。
antとかでビルドするようですが、よく知らないのでシェルでmakeとmake testを実行してます。最初Trac LightningのHudsonでやってたときは、nmakeとnmake testとしていました。
2011年03月30日
Jenkins-CIとEUnitで自動テストを構築
というわけで、ErlangベースのMMOエンジンを構築中です。
ブログのエントリがまるっと止まってしまっていてお恥ずかしい限りですが。。。。
ちまちまとコードを書いているところですが、CI(継続的インテグレーション)ツールのJenkins(旧Hudson)と、Erlangのユニットテストツール「EUnit」を使って、進捗と現状の把握がしやすい環境を整えようとしているところです。
↓のエントリを参考にさせていただきました。
http://shin1o.blogspot.com/2008/02/hudson.html
全体的には、テストコードを用意して、CIのビルドコマンドにテスト実施を指示し、テストはsurefire形式のログを吐き出すよう記述する、という組み合わせになります。
開発本家ブログには、Integrating Eunit test into Hudson(Jenkins) CI process.として記録していたのですが、書き慣れない英語よりも日本語でふつーにメモしておかないとあとで自分が困りそうです。。。
■手順はこんな感じ。
1.テストコードを書きます。さらに、EUnitのテストコードをまとめて呼び出すエントリポイントを作ります。
run_tests_with_log() -> eunit:test([test], [{report,{eunit_surefire,[{dir,"."}]}}]).
2. Makefileに、上記テストを呼び出すコードを書きます。
test: $(objs)
erl -pa . -noshell -boot start_clean -s test run_tests_with_log -s init stop
(このエントリは、testターゲットが test:run_tests_with_log/0 を起動します)
3. Jenkins(Hudson)に、以下のプラグインをインストールします。
Cobertura Plugin
FindBugs Plug-in
4. Jenkins(Hudson)のJobに“make test”を追記。さらに、“Publish JUnit test result report” をオンに、XML Pathを"src/*.xml"などに設定し、レポートXMLを参照するよう指定します。
5. 以上の設定で、自動ビルドのときにテストまで行われるようになります。
ブログのエントリがまるっと止まってしまっていてお恥ずかしい限りですが。。。。
ちまちまとコードを書いているところですが、CI(継続的インテグレーション)ツールのJenkins(旧Hudson)と、Erlangのユニットテストツール「EUnit」を使って、進捗と現状の把握がしやすい環境を整えようとしているところです。
↓のエントリを参考にさせていただきました。
http://shin1o.blogspot.com/2008/02/hudson.html
全体的には、テストコードを用意して、CIのビルドコマンドにテスト実施を指示し、テストはsurefire形式のログを吐き出すよう記述する、という組み合わせになります。
開発本家ブログには、Integrating Eunit test into Hudson(Jenkins) CI process.として記録していたのですが、書き慣れない英語よりも日本語でふつーにメモしておかないとあとで自分が困りそうです。。。
■手順はこんな感じ。
1.テストコードを書きます。さらに、EUnitのテストコードをまとめて呼び出すエントリポイントを作ります。
run_tests_with_log() -> eunit:test([test], [{report,{eunit_surefire,[{dir,"."}]}}]).
2. Makefileに、上記テストを呼び出すコードを書きます。
test: $(objs)
erl -pa . -noshell -boot start_clean -s test run_tests_with_log -s init stop
(このエントリは、testターゲットが test:run_tests_with_log/0 を起動します)
3. Jenkins(Hudson)に、以下のプラグインをインストールします。
Cobertura Plugin
FindBugs Plug-in
4. Jenkins(Hudson)のJobに“make test”を追記。さらに、“Publish JUnit test result report” をオンに、XML Pathを"src/*.xml"などに設定し、レポートXMLを参照するよう指定します。
5. 以上の設定で、自動ビルドのときにテストまで行われるようになります。
2010年01月16日
spawnは慣れるまで「spawn_link」で書かないといけませんね
Erlangで、別プロセスへのメッセージ送信の練習をするのだ−、ということで、メッセージ受信部をspawnしておいて、そのあとメッセージを連投する、というコードを書いてみたのですが、なぜかreceiverのio:formatが受信したメッセージを表示してくれないという現象で一晩ハマりました。
バグとしては単純で、io:formatの追加引数のところをリスト化し忘れていたために表示されなかったのですが、まったくエラーとかが出なかったのでハマッたわけです。
結局、spawnをspawn_linkに置き換えて、生成したプロセスがエラーを発生させて止まったときに、親のプロセスも止まるようにしたら、エラーメッセージが出て原因箇所がつきとめられました。
親に影響が波及しては困るプロセス生成もあるのでしょうけど、そんなのは先の話。ひとまず初心者のうちはspawn_linkと書くようにしますー
--[期待した出力]--------
1> c(procs).
{ok,procs}
2> procs:testit().
Message from 5
Message from 4
Message from 3
Message from 2
Message from 1
ok
3>
-------------------------
--[実際の出力]-----------
1> c(procs).
{ok,procs}
2> procs:testit().
ok
3>
-------------------------
--[spawn_linkに置き換えたときの出力]-----------
10> procs:testit().
Message send 5
Message send 4
Message send 3
** exception exit: badarg
in function io:format/3
called as io:format(<0.25.0>,"Message from ~p~n",5)
in call from procs:receiver/0
11>
-----------------------------------------------
--[動かなかったコード]-----------------------
-module(procs).
-compile(export_all).
testit() ->
Pid = prep(),
timer:sleep(3000),
kicktest(Pid, 5).
prep()->
spawn(?MODULE, receiver,[]).
receiver() ->
receive
{num, A} ->
io:format("Message from ~p~n", A),
receiver();
{stop} ->
io:format("bye ~n");
_Other ->
io:format("Unknown message~n"),
receiver()
end.
kick(Pid, A) ->
Pid ! {num, A}.
kicktest(_ReceiverPid, 0)
-> ok;
kicktest(ReceiverPid, Count) when Count > 0 ->
kick(ReceiverPid, Count),
kicktest(ReceiverPid, Count -1).
-----------------------------------
--[ちゃんと動くコード]-----------------------
-module(procs).
-compile(export_all).
testit() ->
Pid = prep(),
timer:sleep(3000),
kicktest(Pid, 5).
prep()->
spawn(?MODULE, receiver,[]).
receiver() ->
receive
{num, A} ->
% ここ、Aをリスト化し忘れてた↓
io:format("Message from ~p~n", [A]),
receiver();
{stop} ->
io:format("bye ~n");
_Other ->
io:format("Unknown message~n"),
receiver()
end.
kick(Pid, A) ->
Pid ! {num, A}.
kicktest(_ReceiverPid, 0)
-> ok;
kicktest(ReceiverPid, Count) when Count > 0 ->
kick(ReceiverPid, Count),
kicktest(ReceiverPid, Count -1).
-----------------------------------
バグとしては単純で、io:formatの追加引数のところをリスト化し忘れていたために表示されなかったのですが、まったくエラーとかが出なかったのでハマッたわけです。
結局、spawnをspawn_linkに置き換えて、生成したプロセスがエラーを発生させて止まったときに、親のプロセスも止まるようにしたら、エラーメッセージが出て原因箇所がつきとめられました。
親に影響が波及しては困るプロセス生成もあるのでしょうけど、そんなのは先の話。ひとまず初心者のうちはspawn_linkと書くようにしますー
--[期待した出力]--------
1> c(procs).
{ok,procs}
2> procs:testit().
Message from 5
Message from 4
Message from 3
Message from 2
Message from 1
ok
3>
-------------------------
--[実際の出力]-----------
1> c(procs).
{ok,procs}
2> procs:testit().
ok
3>
-------------------------
--[spawn_linkに置き換えたときの出力]-----------
10> procs:testit().
Message send 5
Message send 4
Message send 3
** exception exit: badarg
in function io:format/3
called as io:format(<0.25.0>,"Message from ~p~n",5)
in call from procs:receiver/0
11>
-----------------------------------------------
--[動かなかったコード]-----------------------
-module(procs).
-compile(export_all).
testit() ->
Pid = prep(),
timer:sleep(3000),
kicktest(Pid, 5).
prep()->
spawn(?MODULE, receiver,[]).
receiver() ->
receive
{num, A} ->
io:format("Message from ~p~n", A),
receiver();
{stop} ->
io:format("bye ~n");
_Other ->
io:format("Unknown message~n"),
receiver()
end.
kick(Pid, A) ->
Pid ! {num, A}.
kicktest(_ReceiverPid, 0)
-> ok;
kicktest(ReceiverPid, Count) when Count > 0 ->
kick(ReceiverPid, Count),
kicktest(ReceiverPid, Count -1).
-----------------------------------
--[ちゃんと動くコード]-----------------------
-module(procs).
-compile(export_all).
testit() ->
Pid = prep(),
timer:sleep(3000),
kicktest(Pid, 5).
prep()->
spawn(?MODULE, receiver,[]).
receiver() ->
receive
{num, A} ->
% ここ、Aをリスト化し忘れてた↓
io:format("Message from ~p~n", [A]),
receiver();
{stop} ->
io:format("bye ~n");
_Other ->
io:format("Unknown message~n"),
receiver()
end.
kick(Pid, A) ->
Pid ! {num, A}.
kicktest(_ReceiverPid, 0)
-> ok;
kicktest(ReceiverPid, Count) when Count > 0 ->
kick(ReceiverPid, Count),
kicktest(ReceiverPid, Count -1).
-----------------------------------
2010年01月07日
MacOS上でソース編集するときは改行コードに注意
MacOS XでErlangのソースを書いていてコメントまわりで軽くハマッたのでメモ。
MacOS Xではなぜかコメント(「%」以降、行末までコメント扱い)がうまく動かないという現象が起きていました。
原因は、Erlangの実行環境 erl は、CRのみの改行を改行と認識しないためでした。エディタ上では改行しているつもりでも、erlは長い1行と認識。そのため、コードの途中でコメントをつけると以後の行がすべてコメント扱いとなっていました。
テキストエディタに改行コードの変更機能があれば、それを使ってLFもしくはCRLFを改行コードに設定することで回避できます。
MacOS Xではなぜかコメント(「%」以降、行末までコメント扱い)がうまく動かないという現象が起きていました。
原因は、Erlangの実行環境 erl は、CRのみの改行を改行と認識しないためでした。エディタ上では改行しているつもりでも、erlは長い1行と認識。そのため、コードの途中でコメントをつけると以後の行がすべてコメント扱いとなっていました。
テキストエディタに改行コードの変更機能があれば、それを使ってLFもしくはCRLFを改行コードに設定することで回避できます。
2009年12月22日
Erlang Super Lite Chapter3に参加予定
あまりに手が止まってしまっているので、勉強会に参加して無理矢理にでも手を動かそうと決心。
ひとまず1月21日のErlang勉強会、Erlang Super Lite Chapter3に参加予定です。おもしろそう。
課題図書は、オライリーの原書、Erlang Programming。さっそく購入して、宿題もぼちぼち解いてますー
ひとまず1月21日のErlang勉強会、Erlang Super Lite Chapter3に参加予定です。おもしろそう。
課題図書は、オライリーの原書、Erlang Programming。さっそく購入して、宿題もぼちぼち解いてますー