2013-02-28

やっぱり基礎って大事だよね


はじめに

元ネタ「プロセス間の期限付き排他ロック」が前の記事と一緒ですが、誰かを貶めたり中傷しようとしたりというのではなく、勝手に上から目線で「基礎知識が足りてないよね」とミサワ的な発言をすることを目的とした記事となっております。ご承知ください。

仕様のここがまずそう

「実装」のセクションに書いてある
  • シンボリックリンクを使って排他制御する 
  • 期限切れは、シンボリックリンクそのものの mtime で表現する
    • mtime <= now なら期限切れ
    • つまり、シンボリックリンクの mtime を期限が切れる時刻(未来)にする

ですが、「ロックとは何か」とか「アトミックな操作とは何か」あたりの基礎知識がある人にとっては、ちょっと考えればまずいのがすぐに分かります。シンボリックリンクの作成symlink(2)とmtimeの変更utimeutimensat(2)は別々のシステムコールであり、両者をアトミックに行えるシステムコールがありません。

すなわちsymlink(2)が成功したとして、その次にutimeutimensat(2)が実行されるまでの「間」というものが存在し、その間に他のプロセスが色々するチャンスがあるわけです。特に、作成されたシンボリックリンクはutimeutimensat(2)が実行されるまでは「期限切れ」状態にあるとみなせます。その結果、utimeutimensat(2)の実行時点で持っているシンボリックリンクは競合するプロセスから見れば期限切れなので、そのシンボリックリンクは削除されてしまう可能性があるわけです。

この一点に関しては、symlink(2)でのリンク先としてTTLをセットするということで、期限が未来であるロックをアトミックな操作で作成できます。しかし、期限切れのロックファイルをどう取り扱うかという点にも問題があるので、そう簡単にはいかないというのは元記事のコメント欄にあるとおりです。

ちなみに、その期限切れロックファイルの扱いの問題を同じようにシステムコールレベルで見れば、statlstat(2)とunlink(2)をアトミックに実行することはできない(ので、その為に別のロック機構が必要になっちゃう)よねってな話になるかと思います。

もったいない

さて超上から目線で偉そうですが、「知識が足りないのってもったい無いよね」というのが元記事に対する感想です。元ネタの著者の方がどのような方かは存じ上げませんが、
  • コードは読みやすい
  • コメント欄でのディスカッションで自分で駄目なパターンに気づいている
ことからして聡明な方だとお見受けします。にもかかわらず、上記の通りの仕様を作成してしまうというのは、アトミックな操作とかクリティカルセクションに対する排他制御とかいうのを経験的には知っていても体系的な知識としては獲得していないのではないかと想像します。

一般的にこの手の「車輪の再発明」をする場合、最初の車輪はどうやって発明されたのかをちゃんと学んでおかないと、見た目は派手だけど動かない車輪を発明したり、紆余曲折を経たら全く同じものができてしまったなんてことが起きてしまいます。そういうのも経験として重要な場合もあるとは思いますが、できればそうした無駄は省けたほうがいいんじゃないかとオジサンは思うわけです。

追記

symlinkのmtimeっていじれない?あれ?と思って調べたらutime(2)じゃなくてutimensat(2)とかいうのを使うらしい。そんなmanページ見たことないなーと思ったらhttp://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/futimesat.2.html
はあれどutimensatはなし。

って、書いてて気づいたけど、システムコールでのsymlinkのハンドリングって気をつけなきゃいけなかった、ってことはstat(2)もlstat(2)にしなきゃだめですね。

てなわけで、偉そうなこと言いたいのなら知識の吸収は継続しないといけないよねという反省と、ちゃんとJMにコミットしないといけないよねという反省でしめさせていただきます。

2013-02-27

関数名は大事だよね

プロセス間の期限付き排他ロックについて。

既にコメントが付いてるので、設計レベルでどうだとか、というのはおいておきます。

とにかく実装をパッと見て、これはダメそうだなというのは
52 sub lock {
中略
62             $self->unlock;
63         }
64     }
65     if (symlink $target, $self->file) {
あたりで気づけるのではないでしょうか。lock関数の中で、実際にlockを獲得しているのが65行目のsymlinkなのに、その手前でunlockを呼んでます。一般的に言って、獲得してないロックを解放するのはかなりまずいでしょう(実際、このunlockの呼び出しの直前で他のプロセスがlockに成功すると、そこで生成されたロックファイルを勝手に削除しちゃうことになり、結局2重にlockが獲得できちゃいます)。

さて、標題の「関数名は大事だよね」ですが、unlockの実装は
73 sub unlock {
74     unlink $_[0]->file;
75 }
だけなので、下手するとunlinkってな名前を付けちゃいそうです。が、実装がたまたまそうなってるだけで、呼び出し側から見ればやりたいことはunlockなのですから、この名前で大正解。これがunlockと命名されていたからこそ、lock関数の実装がダメそうなのがすぐに分かったわけです。

というわけで、関数名を横着しないできちんと付けてると、コードの間違にも気づきやすいよねというお話でした。

prometheusのrate()関数の罠

 久しぶりのAdventカレンダー挑戦、うまくいく気がしません。 閑話休題。実のところ、rate()関数というよりは、サーバー側のmetric初期化問題です。 さて、何らかのサーバーAがあったとして、それが更に他のサーバーBにRPCを送っているとします。サーバーBの方でホワイトボ...