Partty 端末スピード

ttyには「端末スピード」というパラメータがあって、これは端末エミュレータ(xtermとかiTermとか)ごとに違っている。

で、コレを越える速度で端末に書き込もうとすると、端末が壊れる。壊れる。

端末は非同期にIOするらしくて、端末スピードを超えた速度で書き込みを行っていると、中のバッファがどんどん消費されていくっぽい。
で、バッファが無くなると壊れる。ついでにwriteがEFAULTで失敗する。そんな。EFAULTって内部バッファも含むのか!


iTerm.appの端末スピードは遅い。Terminal.appの端末スピードは速い。
で、Terminal.appをParttyのサーバー側、iTerm.appをクライアント側にすると、iTerm.appはすぐ壊れる。すぐ壊れる。仮想端末の端末スピードはサーバー側に合わせられているので。

それから、サーバー側単体でも、バッファの転送の時に微妙に速度が揺らぐと、タイミングの問題で端末スピードを超えてバッファに書き込もうとする瞬間が発生→壊れる。


PuTTYでもたまに発生する(Parttyを使うといつも発生する)、画面中にPuTTYPuTTYPuTTYPuTTYと表示される現象も、どうやらコレっぽい。



Parttyでは、一つの仮想端末に複数のクライアントがぶら下がるので、端末スピードは一番遅い端末に合わせないといけない…かというと、そうでもなくて、端末スピードを合わせても、プロセス間通信で速度に揺らぎが出るので、タイミングによっては壊れる。これはマズイ。

というわけで、クライアント側で、クライアントはクライアント側で、しっかり端末に書き込まれるまで、次の文字を読み込まないようにしてみた。その間はサーバーに待っていてもらう。
tcdrain()という関数(中でioctlほげほげを呼んでいるっぽい)があって、コレを使うとバッファが実際に書き込まれるまでブロックする。


で、その間はサーバー側のプロセス間通信がブロックされる…かと言うと、O_NONBLOCKにしているのでブロックしないから…ビジーループになる。CPU使用率100%。やったね。

で、これ解決する方法は3つ。

1.ビジーループ?問題なし!

2. write()する前にO_NONBLOCKを解除して、終わったらまたセット

3. dup()を使ってファイルディスクリプタをコピーして、片方はO_NONBLOCKをセットしてread()とポーリング用に使って、もう片方はO_NONBLOCKをセットしないでwrite()用に使う


1.は潔い。スバラシイ。悪くない。

2.の問題は、fcntlを大量に呼ぶことになるので、やっぱりCPUを喰うし、パフォーマンスも落ちそう。コンテキストスイッチ乱舞。

3.だと、ファイルディスクリプタを通常の2倍消費してしまう。dup()が意図通り動くかワカラン。


ファイルディスクリプタの最大数は、OSによって違うし変えらるけど、デフォルトでは1プロセスに付き1024くらいらしい。(Macだと調べ方がワカラン)

3.案にすると、クライアントの最大数は、

(1024 - 標準入出力3つ - TCPソケット1つ)/ 2 = 510台

…まぁこれは全然問題ないけど、むしろリソースの問題かな。
(ファイルディスクリプタが足りなくなったら、間にリピータを挟むとか)

ファイルディスクリプタをたくさん消費するとメモリを喰う?