2011年7月26日火曜日

toppers/tinetの不具合(と思われる)現象…

症状:tcp_rcv_buf()で受信待ち状態にあるとき、通信相手が切断(FIN)してもtcp_rcv_buf()から戻ってこない。

基本的にtinetの実装では、このような受信待ちのときの相手からの切断にはキチンと対応しようとしているようだ。つまり、tcp_***_***()API呼び出しでブロッキングされているときFINが来たときは、そのブロックを解除して0を返すような配慮がなされている。しかし、うまく機能していない場合があるようだ。

問題のtcp_rcv_buf()呼び出しだけれど、デバッガで追いかけると最終的にはtcp_wait_rwbuf()関数内のtwai_flg(cep->rcv_flgid, TCP_CEP_EVT_RWBUF_READY, ...)でいったんブロックされる。受信データがあるか、FINを受信するとフラグがシグナル状態になってtwai_flg()から抜けてくるのだけれど、その後この関するから抜けるための条件(tinet/netinet/tcp_subr.cの957行目)を満たさないのでreturnしないで、再び上のtwai_flg()で待ちに入ってしまう。さて、これをどうやって修正するか。

整理すると、tcp_wait_rwbuf()関数から抜けられずにループしてしまうのが問題。
修正方針としては、tcp_subr.cの957行目にある条件式に新しい条件を追加、つまり、FINを受信したと思われるとき「も」条件を成立させる式を追加することによって関数から0をreturnするように変更する方法もあるが、条件式に意味が100%完全に把握できているわけではないので、安易な変更はデグレの可能性もある。例えば、追加した(とする)条件によって、FINを受信していないのに0をreturnするようになると、アプリ側は接続が切断されたと勘違いしてしまいかねない。

デバッグで動作を追ってみると、プロトコルスタックはその後tcp_input.cにあるclose_connection()と関数を呼び出していることが分かった。その関数内で、受信端のステートマシンをhalf-closedの状態に遷移させている。このタイミング利用して、twai_flg()で待ちになっているフラグをもう一度set_flg()することにしてはどうだろうか?

具体的には、tcp_input.cの
*********************
*** 1367,5 ****
--- 1367,6 ----
   switch (cep->fsm_state) {

   case TCP_FSM_SYN_RECVD: /* SYN を受信し、SYN 送信済み */
   case TCP_FSM_ESTABLISHED: /* コネクション開設完了 */
      cep->fsm_state = TCP_FSM_CLOSE_WAIT;
+    syscall(set_flg(cep->rcv_flgid, TCP_CEP_EVT_RWBUF_READY));
      break;
*********************

と、やってはどうかと。

とりあえずは期待通りの動作になったっぽいので、これでしばらく様子を見る。

0 件のコメント:

コメントを投稿