NTTグループの各社で鳴らした俺たちLinuxトラブルシューティング探偵団は、各社で培ったOSS関連技術を手に、NTT OSSセンタに集められた。普段は基本的にNTTグループのみを相手に活動しているが、それだけで終わる俺たちじゃあない。引き続きOSSに関するトラブルの解決過程を@ITで連載していくぜ。
ソースコードさえあればどんなトラブルでも解決する命知らず、不可能を可能にし、多くのバグを粉砕する、俺たちLinuxトラブルシューティング探偵団! 助けを借りたいときは、いつでもいってくれ!
OS:高田哲生
俺はリーダー、高田哲生。Linuxの達人。俺のようにソースコードレベルでOSを理解している人間でなければ、百戦錬磨のLinuxトラブルシューティング探偵団のリーダーは務まらん。
Web:福山義仁
俺は、福山義仁。Web技術の達人さ。ApacheのようなWebサーバからTomcat、JBossみたいなJava、アプリケーション技術まで、何でも問題を解決してみせるぜ。
DBMS:下垣徹
下垣徹。PostgreSQLの達人だ。開発からサポートまで何でもやってみせらぁ。でも某商用DBMSだけは勘弁な。
HA:田中 崇幸
よぉ! お待ちどう。俺さまこそHAエキスパート。Heartbeatを使ってクラスタを構成する腕は天下一品! HAが好きなんて奇人? 変人? だから何? HaHaHaHa!!
2回目にして、早くも俺、高田哲生の登場だ。「リーダー」ってのが名前負けしてないってとこを見せてやるぜ!
ネットワークアプリケーションの定石、select()+recvfrom()
ネットワークプログラミングにおいて重要な関数に、select()という関数があります。
select()は、サーバプログラムを作成したいときなどによく使われる関数で、複数のソケットを監視し、パケットが届いているかどうかを確認することができます。こうしてパケットが届いたことを確認したうえで、recvfrom()などの受信用関数でパケットのデータを読み出すのが定石となっています。
この仕組みを利用していたとあるシステムで、recvfrom()の処理中にアプリケーションが停止してしまうという障害が発生しました。今回はこの障害についてお話ししましょう。
まずは現状把握、正確な情報の取得から
今回問題が生じたシステムは、典型的なサーバ/クライアント構成です。OS上に顧客が独自に開発したアプリケーションが載っており、そのアプリケーションに多くのクライアントがアクセスしてくるというシンプルなシステムになっています(図1)。
サーバOSとして搭載されているのはRed Hat Enterprise Linux ES release 4 Update 4(RHEL4)です。特別なソフトウェアが入っているわけでもない、ごく標準的なシステムといえます。
さて、顧客へのヒアリングによって判明した今回のシステム障害の特徴は、以下のようなものでした。
- recvfrom()が復帰せずにアプリケーションが停止してしまう
- 障害に関して再現性はあるが、発生条件は不明
- 実装言語はCで、select()、recvfrom()をブロッキングモードで利用している
- プロトコルとしてはUDP/IPを利用している
- OSの上に直接、顧客が作成したアプリケーションが実装されている
障害切り分けには、上位レイヤから切り分ける方法と下位レイヤから切り分ける方法の2つのアプローチがありますが、今回は上位レイヤであるユーザーアプリケーションから切り分けていくことにしました。
ユーザーアプリケーション側から問題を切り分けていく場合、可能であれば、実際に動いているアプリケーションのソースコードレビューを行うのが近道です。今回も顧客にそのように提案し、停止している部分の周辺ソースコードを提供してもらいました。
ソースコードレビューで実装をチェック!
問題のソースコードを見てみると、今回のアプリケーションでは、select()を次のように使っていました。以下に非常に簡素化したソースコードを挙げます。さて、何が悪いのでしょうか?
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
そう、select()の返り値を確認していません。
「どうせ、パケットが届いてるかどうかの判定はFD_ISSET()でするんだし、これでもいいじゃないか」という声が聞こえてきそうですが、それは違います。
select()には、select自体のマニュアル以外に、select()の動作について非常に詳しく説明してあるselect_tutのオンラインマニュアル(注1)があります。特にその中の「SELECTの掟」は非常にためになるものです。このマニュアルはselect()の返り値に関して、以下のように言及しています。
3. select()コールの終了後に結果をチェックして、適切に対応するつもりのないファイルディスクリプタは、どの集合にも加えてはならない。
よって、recvfromでデータを受信する前には必ず、selectの返り値が1以上であることを確認しなければならないのです。
注1:注1:Linuxシステム上で「 man 2 select_tut」とコマンドを発行することで参照できます
アプリケーションは犯人ではない?
そこで、顧客にselect()の返り値をきちんと確認するよう提案したところ、「そのようにしてみる」とのよい返事。
「いやー、今回の問い合わせは楽だったなー」とくつろいだのもつかの間、再び問い合わせがありました。今度は何と、select()が成功し、かつFD_ISSET()が真のソケットであるにもかかわらず、recvfrom()で固まる事象が発生したというのです!
しかもよくよく聞いてみると、実はこのシステム、納品前の品質チェックを間近に控えている段階とのこと。これは一大事です(注2)。こうして事件はより重く、深く、分かりにくーい事態へと発展していくのでした。
注2:注2:トラブルシューティングにおいて、緊急度が急に跳ね上がるというのは非常によくあるパターンです。困りますが、往々にしてやむにやまれぬ事情があったりするわけで……
Copyright © ITmedia, Inc. All Rights Reserved.