Gauche:I/Oタイムアウト

Gauche:I/Oタイムアウト

Shiro(2010/04/06 14:20:22 PDT): I/Oにタイムアウトつけられたら便利かな、という考察。

発端は、Chaton-Twitterブリッジで、たまにtwiter->chatonのスレッドが止まる、という問題。 コネクションがエラーになったらリカバリするコードは入っているのだけれど、 エラーにもならずにスレッドが永遠に受信待ち状態になってしまう。 これは「通信の相手側がコネクションの後始末をせずに消えた場合、 こちら側から何らかのアクションを起こさない限り、 相手が消えたことがわからない」という問題で、 どんなtcpアプリケーションにでも起こり得る。

(udpでももちろん起きるけど、udpの場合はもともとunreliableなこと前提で アプリを組むからなあ。tcpの場合、大抵は異常状態をトランスポート層で 検出してくれることをあてにするので、ちょっと盲点ではある)

(tcpの場合、keepaliveを使えばこの状態を検出できるけど、keepaliveの pingのデフォルト値は2時間とかだし、OSによってはこの値を簡単には変えられないので いまいち使いづらい)。

で、この事例そのものはtwitter apiを呼ぶスレッドに対してもう一つの スレッドがタイムアウトを監視してやればいいんだけれど、どんなアプリでも 起き得るならライブラリ側にタイムアウトを組み込みにしちゃったらいいんじゃないか、 と思った。

どのレイヤでやるか?

レイヤを問わずにタイムアウトかけるには、ブロックするかもしれない 処理を別スレッドで走らせてタイムアウトつきthread-join!を呼んでやれば いいんだけれど、タイムアウトした時に実行中のスレッドを止める方法が ちょっと厄介だ。thread-terminate! は有無をいわさずスレッドを殺しちゃうので、 あまり常用したくない (Gauche本体については一応cancellation safeになってるはずだけど、 外部ライブラリを呼んでたりするとその先でどうなってるかわからん)。 シグナルなら中断に対してはより安全だけれど、 シグナルハンドラの設定はグローバルに影響を与えちゃうので ライブラリとしては使いにくい。

素直な方法は、システムのファイルディスクリプタから読み出すレイヤに select()をかませることだ。ところがこのレイヤはポートの下にある。 httpなどの上層ライブラリはread-lineなどポートの上の層を呼んでるので、 ポートより下層でタイムアウト処理をやった場合、どうやって 上層と下層をコミュニケートさせるか。

下層から上層へは、タイムアウトが発生したら例外を投げるという 安直な方法がある。

上層から下層へはちょいと難しい。引数で指定させるようとすると、 上から下まで全ての中間レイヤに引数をつけてまわらないとならない。

下層でタイムアウト処理をさせるとして、もうひとつ、 レイヤのミスマッチは「タイムアウト」の定義にも影響を与える。 つまり、どの時点をタイムアウトの起点とするか。 http-getにタイムアウトをかけるなら、http-getを呼んだ時点を 起点とするのがいいけれど、 下層のread(2)を呼んでるコードにとっちゃ上の事情はわからない。

ああ、pthread_cond_timedwait()とかが絶対時間でタイムアウトを 取るようになってるのはこのせいかもな。上層で絶対時間を指定してやれば 下層は起点を気にする必要がない。

上層のAPIとしては相対時間を受け入れるけれど、動的変数でタイムアウトを 指定する段階で絶対時間にしとけばいいか。

More ...