トーフサロン

 

2013-03-31

起業しました

@, @ にくっついて株式会社時雨堂 (かぶしきがいしゃしぐれどう) を起業しました (領収証をもらうときの宛名の説明が面倒くさい。前株にしぐれ、ときのあめと書いてしぐれのしぐれです、それにどうどうのどうでしぐれどうです) 。社長 (@) の退職と起業の件もご覧下さい。格ゲー部部長です。ドラクエ10をはじめましたが知り合いがいません。

まだ会社のWebサイトがないのですが、主にネットワーク関係の仕事をしていくのだろうと思います。役員 (取締役) なのにあまり会社について知りません。権限は最小限です。私の仕事は iOS, C, ドキュメント作成が中心になりそうです。

さあ張り切って働くぞーと言いたいけど、どうも一年半ほど前に体調を崩してから精神的に踏ん張りがきかない。身体的には回復したと思うけれど、いや回復しきってなさそうだなこれは。ものによってはマンガ一冊読むのも疲れるから、一日一話とか何日にも分けて読んだりして。二度と回復しない類のもののような気もしてくる。

とにかく今年は限られたリソースを仕事に最優先で割り当てて、着実にこなしていきたいと思います。

tmaedaatmaedaa 2013/04/01 09:58 おぉ。がんばってください。

carvercarver 2013/04/01 10:10 ありがとうございます。

2012-06-04

[] NIF におけるメモリアロケーションエラー時の対処

NIF を作る上でずっと気になっていた、 NIF の API を使ったメモリアロケーションに失敗した場合にどうすればいいのか、何ができるのかを調べてみた。ここで使ったソースコードは https://github.com/szktty/erlalloctest に置いてある。

アトムの動的な生成

Erlang のアトムは GC に回収されず、一度生成するとメモリに残り続ける。そのため異なる内容のアトムを自動的に生成するコードを書くと、メモリの使用量が増え続けることになる。「メモリリークの原因になるから」と言われて自分でも注意していたが、実際にどうなるのか試してみた。

1> erlalloctest:make_create_atom_error().

Crash dump was written to: erl_crash.dump
no more index entries in atom_tab (max=1048576)

アトムを一定数 (1048576) 以上生成しようとすると、クラッシュダンプが生成され、上記のエラーメッセージが表示されてプロセスが落ちる。では肝心のメモリ使用量はというと、 Mac OS X と VMware 上の CentOS 6.2 で試したところ 0.1MB も増えなかった。 VM があらかじめ確保したヒープ内で完結しているのかもしれない。どちらにしろアトム 1048576 個程度ではたいしたサイズにならないだろうし、メモリリークとまではいかないと思う。ただし少量でもアトムを動的に生成してしまうコードはいつか落ちるので、注意するに越したことはない。Vの字の言うことは話半分に聞いとけ。

解決策というか回避策としては、任意の内容のアトムを生成できるような設計にはしないこと。ユーザーの ID や IP アドレスをアトムで管理するなどしない。文字列などアトム以外の項で代用する。

※追記: コメントを受けて起動コマンドのオプションを調べたら、 "+t size" でアトムの最大数を設定できるらしい。この値を大きくしたら動的にメモリが確保され続けるのかもしれない。設計とパラメータの調整で気をつけられるから、コードレベルで気にするほどではないかなあ。

enif_alloc

enif_alloc は alloc と同様、任意のサイズのメモリブロックを確保する NIF 関数。この関数で確保したメモリも GC に回収されず、 enif_free を使わないと解放できない。

enif_alloc で 1 バイトのメモリブロックを延々と確保するコード (erlalloctest:make:make_alloc_error/0) を VMware 上の CentOS 6.2 で実行すると、メモリとスワップを使い尽くした後にプロセスが落ちた。 Erlang レベルに戻り値も例外も届かず、 enif_alloc 直後に置いた printf も機能しない。 NIF でできることは何もなさそう。

アトム以外の項の生成

enif_make_* でアトム以外の項を生成し続ける。こちらも VMware 上の CentOS 6.2 で実行すると (erlalloctest:make_term_error/0) 、 enif_alloc と同じくメモリとスワップを使った後にプロセスが落ちた。 Erlang レベルに戻れないのも printf が機能しないのも同じ。ただし、 enif_alloc の場合ほどにはメモリもスワップも消費しなかった。こんな感じの単純に生成し続けるだけのコードだから GC が動いて落ちないと思ったんだけど、 NIF であまりにも大量に項を生成する場合は注意が必要かもしれない。

   while (1) {
     e = enif_make_int(env, rand());
     if (e != 0) {
       l = enif_make_list1(env, e);
       if (l == 0) {
         return error_term;
       }
     } else {
       return error_term;
     }
   }

この場合も NIF でできることは何もなさそう。 VM のソースを見ると、 GC でメモリが足らない場合はエラーメッセージが表示されるらしいコードがあるから、落ちる前に何かしら表示される場合もあるかも。

※追記: @kenji_rikitake さんより「 ErlNifEnv の中で作られた ERL_NIF_TERM は消えない」とのこと。ありがとうございます。 ERL_NIF_TERM の説明にしっかり書かれてた。つまり enif_make_* で生成した項は、生成時に渡した ErlNifEnv を enif_free_env で解放しない限りメモリに残る。 Erlang レベルに渡せば GC で回収されるが、 NIF では回収されないので要注意。

Variables of type ERL_NIF_TERM can refer to any Erlang term. This is an opaque type and values of it can only by used either as arguments to API functions or as return values from NIFs. All ERL_NIF_TERM's belong to an environment (ErlNifEnv). A term can not be destructed individually, it is valid until its environment is destructed.


どうも結論は「何もできないから何もしない」に落ち着くのかな。 enif_alloc や enif_make_* の戻り値をチェックする意味はなさそう。

VoluntasVoluntas 2012/06/04 15:58 atom のサイズは設定で変更出来ちゃうので、多めにとってしまう&動的に生成するとメモリーが淡々が増え続けるという現象が起きたりします。

ロングラン試験とかやったさい顕著に出ちゃうんですよね ... 。

ita-wasaita-wasa 2012/06/04 15:59 atom 領域は起動時に確保だったような気がする。

VoluntasVoluntas 2012/06/04 16:03 じゃぁ基本的には決まり切ったメモリしか食べないって事か。ただ淡々と増え続けてある規定の数越えたら VM が落ちるだけかー。

2012-02-14

[] [erlang-questions] Erlang と BEAM の未来 (The future of Erlang and BEAM)

[erlang-questions] The future of Erlang and BEAM を翻訳。元の投稿 (StackOverflow に投稿された質問 を erlang-questions の識者に尋ねてみたもの) への Joe Armstrong からの返信。

これで Erlang に興味を持った人は「 Erlang に興味を持った人へ (id:Voluntas:20110319) 」をどうぞ。

元の投稿

エリクソン、Facebook、ゴールドマンサックスなどの会社が Erlang を業務に取り入れているのを見てきて、 Erlang に強く興味を持っていました (C++/PHP/Java の世界から来ました) 。低レイテンシで、他の言語 (私にとっては Java とか) よりもずっとすっきりして (訳注:言語仕様?) よく出来ている Erlang なら、過酷な負荷に耐えるアプリケーションを開発できる最高のプラットフォームになると思ったのです。

ところがそんな「驚き ("wow effect") 」も冷めた頃、 Erlang が最適と思われた難題の数々 (リアルタイム処理、低レイテンシの要求されるアプリケーション、並行性、耐障害性など) を解決できそうな高パフォーマンスの Java ライブラリをいくつも見つけました。中には LMAX Disruptor 並行フレームワーク (注:トレーディングシステムのプラットフォーム LMAX の並行プログラミングフレームワーク) のように BEAM では実現できないものもありそうです。

そこで質問です。高負荷に耐えるアプリケーションの開発プラットフォームとして、今でも Erlang は最適だと言えるのでしょうか? Erlang にこだわるなら、少ないリソース (OTP チーム対 JVM チーム + サポーターなど) で Erlang を他の環境に追いつかせるほうが、相当に成熟した (J) VM をさらに詰めるよりもいいのでしょうか? 本当に BEAM でこのような戦略を取り、高パフォーマンスを実現できるのでしょうか?

先に断っておきますが、私はこの議論を炎上させたくありません。純粋に答えを知りたいだけです。 Erlang が大好きで、最高のプラットフォームだと思っていて、実プロジェクトに Erlang を使って全力を尽くしたいだけです。答えてくれる人がいるなら知りたいし、私が間違っているなら訂正してくれると思います。

Joe Armstrong からの回答

短いですが、質問にお答えしようと思います。

そもそもあなたはチョークとチーズを比べています (訳注:「全然違う」を意味する例え) 。Erlang は高速なメッセージパッシングを行う言語として設計されませんでした。耐障害性を備えたアプリケーションを構築できるように設計されました。速度を目的には設計されませんでした。安全を目的に設計されました。

あなたが Erlang と比べたメッセージパッシングプラットフォームの多くは速度を優先し、安全性は二の次に設計されています。エラーを考慮しないプラットフォームが Erlang より高速だとしても、まったく驚きません。

Erlang は処理におかしな点がありそうなときにこそ能力を発揮します。コードにバグがあってクラッシュしたり、システムをオーバーロードしたり、思いも寄らない挙動に悩まされる場合です。

Erlang の考え方は他の言語と全然違います。

Web サーバを例としてみましょう。これまでの考え方では、一台のマシンに一つの Web サーバがあるとすれば、タスクが要求されるたびに OS のプロセスを生成するか (安全) 、すべてのセッションを同じタスクで管理するでしょう (速いけど安全ではない) 。*1 Web サーバは安全上の理由で OS のプロセスを使います。ユーザのセッションそれぞれにプロセスを割り当てれば安全ですが、かなり遅くなります。

もし Web サーバのソースコードに稀に発生するエラーがあったらどうなるでしょうか? ごく稀にしか現れないエラーだとします。このエラーはすべてのユーザのセッションに影響するのか、エラーの発生したセッションのみに影響するのか? おそらく Web サーバが潰れたらセッションもすべて潰れるだろうと思います。

Erlang なら Web サーバをどう実装するのか? 表面上は他の Web サーバと似ていますが、ユーザのセッションを 2000 抱えた Erlang システムは、 2000 のセッションを管理する一つの Web サーバではありません。それぞれが完全に独立した Erlang プロセスが 2000 あり、一つ一つが完全に独立した Web サーバとして動作します。

つまり Erlang では 2000 の Web サーバがあり、それぞれユーザのセッションを一つだけ抱えて動作します。これまでは一つの Web サーバで 2000 のセッションを抱えるのが普通でした。

話を戻します。あるセッションでごく稀にしか発生しないコード上のエラーに遭遇したらどうなるでしょうか? Erlang なら一つの Web サーバだけが落ちて、他の Web サーバはそのまま動き続けます。 2000 のセッションをまとめて管理する Web サーバは高速ですが、すべてのセッションが死にます。

では、速さと安心のいずれかをお選びください。超高速な Erlang が欲しいと言うなら間違いです。そんな Erlang は安全ではありません

エラーチェックとエラーからの復帰にはコストがかかります。ただで手に入るものなどありません。

時折「Erlang っぽいモデル」でより高速だと言われるメッセージパッシングシステムを目にします。

ですがそう言ったシステムは安全ではなく、「Erlang っぽいモデル」は高速なメッセージパッシング以外の意味も含んでいます。

Erlang は耐障害性を備えたソフトウェアリアルタイム分散システムとして、なおかつ動的にコードを差し替えられるシステムとして、設計の意図通りにうまく動いています。これにわずかでも近いシステムがあるとは聞いたことがありません。

上辺だけのシステムは山ほどありますが、障害に強いシステムを作るに値するシステムはありません。

*1:原文は "the web server might spawn an OS process for each incoming task (if it wants safety) of it might handle all sessions in the same task if it wants speed but no safety" ですが、 "or" の間違い?

2011-08-27

[] crypto モジュールで VM が落ちる

というバグ報告を erlang-bugs に投稿 (+ 訂正) しました。確認した環境は Erlang R14B03, Mac OS X 10.6.8 (i386, MacBook Air) ですが、特に環境に限定されないと思います。せっかく見つけたんでこっちにも詳細書いときます。

次のメッセージダイジェスト関数に、あるバイナリデータをコンテキストとして与えると VM が落ちる可能性があります。

  • md4_update/2
  • md4_final/2
  • md5_update/2
  • md5_final/2
  • sha_update/2
  • sha_final/2
  • hmac_update/2
  • hmac_final/2

これらの関数は引数にコンテキスト (メッセージダイジェストの計算途中のデータ) を取ります。このコンテキストはバイナリ型で、次の条件を満たすバイナリを与えると VM が落ちました。

  • コンテキスト初期化関数 (*_init 、例えば SHA1 なら sha_init/0) で生成された正当なコンテキストと同じ長さ。試した環境では、 md5_init/0 なら 92 バイト、 sha_init/0 なら 96 バイトでした。
  • 正当なコンテキストのバイト列を逆順にする。理由は後述。

例えば、コマンドラインで次のように入力すると落ちます。

$ erl
Erlang R14B03 (erts-5.8.4) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.4  (abort with ^G)
1> C1 = crypto:sha_init().
<<1,35,69,103,137,171,205,239,254,220,186,152,118,84,50,
  16,240,225,210,195,0,0,0,0,0,0,0,0,114,...>>
2> C2 = list_to_binary(lists:reverse(binary_to_list(C1))).
<<0,0,0,0,213,8,1,216,13,25,10,215,13,17,10,64,23,213,13,
  35,59,35,213,13,48,130,19,217,13,...>>
3> crypto:sha_update(C2, "hello").
Segmentation fault

逆順のバイナリでも、長さが異なれば落ちません。

1> C1 = crypto:sha_init().
<<1,35,69,103,137,171,205,239,254,220,186,152,118,84,50,
  16,240,225,210,195,0,0,0,0,0,0,0,0,19,...>>
2> C2 = list_to_binary(lists:reverse(binary_to_list(C1))).
<<0,0,0,0,3,3,67,240,80,5,125,67,99,77,45,71,72,1,72,77,
  61,3,67,64,242,67,71,77,43,...>>
3> C3 = list_to_binary(binary_to_list(C2) ++ "abc").
<<0,0,0,0,3,3,67,240,80,5,125,67,99,77,45,71,72,1,72,77,
  61,3,67,64,242,67,71,77,43,...>>
4> crypto:sha_update(C3, "hello").
** exception error: bad argument
     in function  crypto:sha_update/2
        called as crypto:sha_update(<<0,0,0,0,3,3,67,240,80,5,125,67,99,77,45,71,
                                      72,1,72,77,61,3,67,64,242,67,71,77,...>>,
                                    "hello")

この原因は crypto モジュールの NIF (Native Implementation Function) 実装に問題があると思われます。 MD5 なら次の二つです。

/* crypto.c: 442 行: md5_init() */
MD5_Init((MD5_CTX *) enif_make_new_binary(env, MD5_CTX_LEN, &ret));
/* crypto.c: 450-451 行: md5_update() */
{
  MD5_CTX* new_ctx;
  ErlNifBinary ctx_bin, data_bin;
  ERL_NIF_TERM ret;
  if (!enif_inspect_binary(env, argv[0], &ctx_bin)
      || ctx_bin.size != MD5_CTX_LEN /* ココ */
  ...
  memcpy(new_ctx, ctx_bin.data, MD5_CTX_LEN); /* ココ */
  ...
}

argv[0] は Erlang レベルで渡された引数のリストです。あまり NIF に詳しくなくてもピンとくる人もいると思います。 Erlang レベルで渡されたバイナリを C の構造体 MD5_CTX として使っています (MD5_CTX は OpenSSL の関数に渡されます) 。問題は、バイナリの長さと MD5_CTX の長さが一致しただけでバイナリを MD5_CTX として使っている点です。つまりバイナリの長さが MD5_CTX と同じであれば、任意の内容のバイナリを OpenSSL の関数に渡せます (先に「バイナリを逆順にした」と書きましたが、厳密には逆順でなくても落ちます。逆順にして確認しただけで、他のパターンは特に試していません) 。まあ大抵はセグメンテーションフォールトで VM が落ちますし、ユーザ入力をコンテキストとして渡すはずがないので、セキュリティ上困らないとは思いますが。

修正方法はおそらく、 Erlang レベルで受け取った引数をそのまま C のデータとして使わないことです。これにはバイナリフォーマットを決めて C レベルでチェックする...必要は今はなく (NIF 以前は必要だった) 、コンテキストをバイナリ型の代わりにリソース型 (リソースオブジェクト) として返せば安全です。てかそのためのリソース型でして、リソースオブジェクトのサンプルコードを探していて、標準ライブラリ中唯一 NIF で実装されている crypto モジュールでリソースオブジェクトが使われていそうな箇所の実装を見て、えー、落ちるだろこれ、あー落ちた、そりゃ落ちるよな、というわけで今に至ります。

これは OpenSSL のバグではなく、同じ crypto でも PHP の crypto 関数のバグとは関係ありません。該当の関数を使い込んでる人でもまったく影響がないと思います。

2011-07-10

[] ガトー・ショコラ・クラシック

久々にお菓子作ってみた。チョコレートを使うと掃除が面倒なんだけど、スポンジケーキにチョコレートを混ぜればいいだけで手軽そうだったから。チョコレートとバターを溶かしたところにメレンゲを混ぜる、ちょっと材料の重いシフォンケーキのような感覚かな?

f:id:carver:20110711090059:image

これに粉砂糖を振る。

f:id:carver:20110711090308:image

参考にした本の写真ほどうまく中央が沈まなかった。生地を型に流したときに、表面をうまく平らにできなかったかもしれない。

f:id:carver:20110711090443:image

カット。まあ自分で食べるには十分。チョコレートはもっといいものを使ってもいいかもしれないけど、高いからなあ。