天泣記

2008-12-07 (Sun)

#1

open3.rb のドキュメントに例をつけているのだが、これがなかなかむずかしい。

もちろん人工的な例なら簡単だが...

#2

php proc_open で検索していると、mecab の話がいくつも出てくる。

うぅむ。そんなに形態素解析したいの?

2008-12-08 (Mon)

#1

サイクルさせてみる。

% ./ruby -ropen3 -e '
r,w = IO.pipe
w.print "ibase=14\n10\n"
Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w)
'
14
18
22
30
42
58
78
106
202
394
718
1394
3462
9102
24894
89506
333010
1736966
11427870
115192042
1619739354
29692865822
774281794358
30458189497450
2398956531470254
352107219881806722
102686460519364138762
84723367480401946387842
136476263524670902999686914
785305095285854273365849051078
13134534737249477581150619911211954
1134814216794427515676428343268138708086
544230844960775772029708309981367306206447062
1427174149203144736442305581082302470658843586444958
36810759341161413784632624227381883193284318700656460051026
10368746353229580094077470340329123360473692180128243435147370961450
...

もっと面白い例はあるか。

#2

ふと、「割れる」と google で検索してみたところ、他のキーワードとして以下のものが出てきた。

爪が割れる   腹筋 割れる 歯 割れる つめ 割れる            トマト 割れる
つめが割れる 唇 割れる   舌 割れる パワーストーン 割れる  ガラスの割れる音

ガラスを想定していたのだが、どうも世の中、爪が割れるようである。

あと、トマトが割れるというのは初めて知った。

#3

ついでに、ruby, perl, python, php を検索してみた。

ruby on rails     ruby 入門 ruby 正規表現     ruby 配列 ruby array
ruby リファレンス ruby cgi  ruby インストール ruby hash ruby string

perl 正規表現 activeperl perl ダウンロード perl ファイル perl split
perl 配列     perl 入門  active perl       perl open     perl 置換

python 入門           python 正規表現 python cgi     python 日本語 python インストール
python チュートリアル python gui      python windows python 配列   python リファレンス

php 入門         php 配列   php date     php マニュアル php サンプル
php リファレンス php 拡張子 php 正規表現 PHP研究所      php インストール

入門、配列、正規表現が共通している。世の中そのあたりが難しいようだ。

#4

あと、C言語

c言語 ゲーム    c言語 ソフト c言語 猫     C言語 入門 C言語 演算子
C言語 ポインタ  C言語 関数   C言語 構造体 C言語 配列 C言語 文字列

正規表現は出てこないのはまぁそうだろう。入門と配列か。

それはそれとして、猫、ねぇ。なんで?

2008-12-09 (Tue)

#1

検索していて、どう書く?org の「入出力の中継」というのを見つける。

Ruby 1.9 の spawn を使えば以下のように書ける。

r1, w1 = IO.pipe
r2, w2 = IO.pipe
pid1 = spawn(ARGV[0], STDIN=>r1, STDOUT=>w2)
pid2 = spawn(ARGV[1], STDIN=>r2, STDOUT=>w1)
r1.close; w1.close
r2.close; w2.close
pid0 = Process.wait
if pid0 == pid1
  Process.kill :TERM, pid2
else
  Process.kill :TERM, pid1
end

だが、書けるからといって役に立つかというと、それほどではない気はする。

プロセスをノード、パイプをエッジとするグラフのうち、役に立つのはかなり制約された形に限られるのではないだろうか。

2008-12-10 (Wed)

#1

複雑なのがあまり役に立たない理由はいくつも考えられる。

他にもいろいろな理由があるだろうが、ひとつでも問題があれば役に立たないとして、一般のグラフからどんどん減じていくと、どんな形のグラフが残るか。

明らかに残るのはふだん shell で使っているパイプラインであり、形としては一直線なものである。

しかし、もう少し残る気がする。

2008-12-12 (Fri)

#1

shell でどのくらいまで作れるかというと、zsh で、木は作れる。

% cat t 
#!/usr/bin/ruby

input = STDIN.read.chomp
s = ARGV[0]
STDOUT.puts input+s
STDERR.puts input+s.upcase
% echo a|./t b >>(./t c >>(./t d) 2>>(./t e)) 2>>(./t f >>(./t g) 2>>(./t h))
abcd
abcD
abCe
abCE
aBfg
aBfG
aBFh
aBFH

わかりにくいのでインデントする

echo a|
  ./t b >>(./t c >>(./t d)
                2>>(./t e))
       2>>(./t f >>(./t g)
                2>>(./t h))

このように、プロセス置換とリダイレクトで、木は作れる。まぁ、すべてのコマンドが終わるのを待たないのはなんだが。

木よりも複雑なものが作れるかというと、coproc を使うことが考えられるが、ひとつしか使えないので、あまり一般的な話にはなりそうにない。

2008-12-14 (Sun)

#1

ちょっと pty を試してみた。

あるコマンドを子プロセスで起動するとして、そのコマンドの入出力を親プロセス側につなぐ場合、pipe を使うのと pty を使うのでなにが違うのか。

わざわざ pty を使う理由は stdio でのバッファリングを fully buffered でなくする、という点にある。

だが、その違いしか起きないかというとそうはいかない。手元の環境 (Debian GNU/Linux) で試したところ、すぐにいくつか違いが見つかった。

2008-12-15 (Mon)

#1

pty で起動したコマンドを普通のパイプラインの一部として使おうとすると、単純にパイプをつなぐようにはいかない。

というのは、EOF がうまく伝播しないからである。

% foo | bar-in-pty | baz

というのが、foo を普通に実行したプロセスと、bar を pty 内で実行したプロセスの入出力をつなぐ、としたとき、pty の master 側を foo の標準出力にすることは可能だが、目的を達成できない。

foo が終了すると foo の標準出力は close されるが、そのとき bar は EOF を検出できない。master 側は bar-in-pty の出力にも使われていて、open したままだからである。

とすると、foo に master 側のを直接植え付けるのではなく、foo の標準出力はパイプにしておいて master に中継し、EOF になったら ^D を送る、というのが必要である。

同様に、bar-in-pty の出力側も、パイプで中継し、master からの読み出しが EIO になったら close するというのが適切であろう。

で、やってみたが、なんか動かない。

require 'pty'

r,w,pid = PTY.spawn("read; exec cat");
p [$$, pid]
system("stty raw -echo", :in=>r, :out=>r, :err=>r)
w.puts

t = Thread.new {
  loop {
    begin
      s = STDIN.readpartial(4096)
      w.write s
    rescue EOFError
      #system("stty -a", :in=>r)
      system("stty -echo icanon eof ^D", :in=>r, :out=>r, :err=>r)
      #system("stty -a", :in=>r)
      #w.write "\n"
      w.write "\x04"
      break
    end
  }
}

loop {
  begin
    s = r.readpartial(4096)
    print s
  rescue Errno::EIO
    break
  end
}

なんか、^D を送る前に \n を送ると動くのだが...

#2

pty でなくても起こるようだ。

以下のようにして、起動後 1秒以内に a を入力し、cooked になるまで待って、その後に ^D を何回入力しても EOF が送られない。

% uname -a
Linux nute 2.6.18-6-486 #1 Mon Oct 13 15:35:23 UTC 2008 i686 GNU/Linux
% ruby -e '
pid = spawn("cat")
system("stty -icanon")
sleep 1
system("stty icanon")
p :cooked
Process.wait pid'
aa:cooked

ここで aa:cooked というのは、最初の a が入力で、次の a が cat の出力、

cooked が p :cooked の出力である。

さて、^D が効かない理由はなんだろう?

効かなくなった後、改行, ^D と入力すると EOF が送られる。

また、raw mode になっている状態でなんの入力もしなければ ^D で EOF がちゃんと送られる。

カーネルな気がするが、どうかな。

#3

まぁ、lnext を使うことにすれば、避けることはできるか。

require 'pty'

r,w,pid = PTY.spawn("cat");
p [$$, pid]
system("stty -echo -echoe -echok -echonl -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -opost", :in=>r)
#system("stty -a", :in=>r)

t = Thread.new {
  loop {
    begin
      s = STDIN.readpartial(4096)
      w.write s.gsub(/[\x00-\x1f]/m) { "\cV" + $& } + "\x04"
    rescue EOFError
      w.write "\x04"
      break
    end
  }
}

loop {
  begin
    s = r.readpartial(4096)
    #p s
    print s
  rescue Errno::EIO
    break
  end
}
#4

FreeBSD だと、とくに問題なく EOF になる。

% uname -mrsv
FreeBSD 6.3-RC1 FreeBSD 6.3-RC1 #0: Mon Nov 26 21:52:21 UTC 2007     root@palmer.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC  amd64
% ./ruby -e '
pid = spawn("cat")
system("stty -icanon")
sleep 1
system("stty icanon")
p :cooked
Process.wait pid'
aa:cooked
ここで ^D でちゃんと終わる

2008-12-16 (Tue)

#1

ふむ。目的が、stdout のバッファリングを変えることなら、 制御端末を変える必要はないんじゃないか?

さらにいえば、stdin や stderr もそのままでよくないか?

単に stdout だけ pty の slave にして、master 側から読み出してパイプに中継すればいい

制御端末を変えないほうが ^C が効いて便利だし、なにか問題はあるだろうか

2008-12-18 (Thu)

#1

pty の確保に特権が必要な OS は残っているか?

調べてみると、DragonFly BSD はまだそうらしい。

FreeBSD は posix_openpt (というか grantpt) を使えば 5.0 から特権不要だが、openpty だと最近まで (FreeBSD-SA-08:01.pty が出るまで) 必要だったようだ。

2008-12-22 (Mon)

#1

セレンディピティなる言葉があるそうな。

解説を読んで、バグの発見能力を連想した。

#2

select で読み出し可能となったらデータを読み込めるか? ただし SSL で。

まぁ、いかにも保証されなさそうである。

試してみよう。

まず、www.dev.java.net:443 に接続する port forwarder をでっちあげる。(このサイトを選んだのは特に他意はなく、https なサイトなら別にどこでもよい。)

また、この port forwarder は接続されてから 1秒経過すると、サーバからクライアントへの転送時に、1byte につき 1秒の休止が入る。最初の 1秒間にそういう休止を入れないのは、最初のネゴシエーションのところはそこで済ますという意図である。

% ./ruby -rsocket -e '
l = TCPServer.new("127.0.0.1", 8888)
c = l.accept
s = TCPSocket.open("www.dev.java.net", 443)
t = Thread.new {
  begin
    loop { s.write c.readpartial(4096) }
  rescue EOFError
  end
}
t0 = Time.now
begin
  loop {
    str = s.readpartial(4096)
    str.each_byte {|byte|
      c.write [byte].pack("C")
      sleep 1 if t0 + 1 < Time.now
    }
  }
rescue EOFError
end
'

クライアントはその port forwarder につないで、SSL で GET リクエストを送って、返事を待つ。IO.select で読み出し可能となったら、(最近新設されてしまった) OpenSSL::SSL::SSLSocket#read_nonblock で読む。

% ./ruby -rsocket -ropenssl -e '       
soc = TCPSocket.new("localhost", 8888)
ssl = OpenSSL::SSL::SSLSocket.new(soc)
ssl.connect
sleep 2
ssl.write "GET / HTTP/1.0\r\n\r\n"
p IO.select([soc])
p ssl.read_nonblock(4096)
'
[[#<TCPSocket:0x82bca84>], [], []]
-e:8:in `read_nonblock': Resource temporarily unavailable (Errno::EAGAIN)
        from -e:8:in `<main>'

そうすると、read_nonblock は EAGAIN になる。やはり。

暗号で 1byte 以上読めるからといって、平文が 1byte 以上読めるわけではないという、それだけの話ではある。

同様な話は zlib とかでの圧縮ストリームでも起きるだろう。

#3

select で readable なら read_nonblock して EWOULDBLOCK なら select から retry、というのは SSL でも単なる TCP でも動く。また、zlib での圧縮ストリームでも (Zlib::GzipReader#read_nonblock を実装すれば) 動くだろう。

これらが動くのは、高レベルの読み込みの中で、ブロックする可能性があるのが低レベルの読み込みだけだからである。

しかし、もし、高レベルの読み込みの実現に、低レベルの読み込みだけでなく、書き込みも行うとすれば、うまくいかないかもしれない。たとえば、読み込んだ後に ack を返すとか。まぁ、理由はともかくとして仮に TCP の上にそういうプロトコルがのっていたとすれば、ack の書き込みでブロックする可能性がある。その場合、select では書き込み可能になるのを待たなければならない。

そうなると、TCP/SSL/GzipReader とは違うコードが必要になる。

こういうのを多態的に扱える non-blocking な API はどんなものが考えられるか?

#4

同様に、違うコードが必要になる例に、TCP と SSL の non-blocking connect がある。

TCP では、ソケットを作った後、non-blocking connect して、EWOULDBLOCK なら select で書き込み可能になるのを待った後、もう一回 non-blocking connect するという流れである。

SSL では、TCP で connect した後、SSL の non-blocking connect を行う。このときのネゴシエーションは、(とりあえず RFC 4346 をちょっと眺めた限りでは) クライアントが送って、受け取って、送る、というように動くらしい。

そうすると、読み込みでも書き込みでもブロックする可能性がある。(一番ブロックしやすいのは受け取るところでだろうが。) したがって、select では読み込み・書き込み可能になるのを、ネゴシエーションの進展にしたがって、適切に待つ必要がある。

で、SSL と単なる TCP の connect を多態的に扱える non-blocking な API はどんなものが考えられるか?

2008-12-23 (Tue)

#1

がんばれ! 猫山先生 1」というまんがを知って、読みたくなった。

で、有隣堂にいったが見当たらない。店頭で検索してみても無いようだ。

で、書泉にいったのだがやはり見当たらない。尋ねてみると、一冊も入っていないとのこと。ていうか、この出版社は直取引とか、まんが扱いじゃないとかだそうな。

まぁ、そういう可能性もあるかな、とは思っていたので、てきとうに本屋にいって買うという方針は変更する。

で、検索してみると、少なくとも、丸善お茶の水店と、ジュンク堂池袋本店にはあることがわかった。

で、丸善で買ってきた。

なお、置いてあったのはまんがの棚ではなく、また、レシートでは医学となっている。

#2

RLIMIT_FSIZE を使うと、ファイルに対して簡単に partial write を起こせるようだ。

% ./ruby -ve '
trap("XFSZ", "IGNORE");
Process.setrlimit(:FSIZE, 1000);
open("/tmp/a", "w") {|f|
  2.times {
    p f.syswrite("a"*2000)
  }
}'
ruby 1.9.1 (2008-12-23 patchlevel-5000 trunk 20946) [i686-linux]
1000
-e:6:in `syswrite': File too large - /tmp/a (Errno::EFBIG)
        from -e:6:in `block (2 levels) in <main>'
        from -e:5:in `times'
        from -e:5:in `block in <main>'
        from -e:4:in `open'
        from -e:4:in `<main>'

そーか。nonblocking でなくても、(といってもファイルに対してはもともと nonblocking は効かないが) partial write が起きることはあるんだな。

2008-12-24 (Wed)

#1

げー。RubySpec が syslog を吐く。

#2

^D が効かないのはやはり Linux の問題であった。

以下で動いた。

パッチ:
--- linux-2.6.27.9/drivers/char/n_tty.c 2008-12-14 08:56:16.000000000 +0900
+++ linux-2.6.27.9/drivers/char/n_tty.c+        2008-12-24 17:41:00.000000000 +0900
@@ -1419,7 +1419,7 @@
                        check_unthrottle(tty);
                }

-               if (b - buf >= minimum)
+               if (b - buf >= (tty->icanon ? 0 : minimum))
                        break;
                if (time)
                        timeout = time;

ただ、対症療法としてはともかく、read_chan の構造自体があまりうまくなくて、これだけで済むのかという話はある。

2008-12-25 (Thu)

#1

close でエラーが生じたとき、fd はクローズされるか? いやまぁ、POSIX からいえば unspecified なんだけど、実際にはどうなるか?

POSIX で起こるとされているエラーは EBADF, EINTR, EIO で、EBADF はそもそも open されてないので興味がない。EINTR, EIO はどうやったら起こせるか分からない。

というわけで試しようがないなぁ、と思っていたのだが、ふと、Linux のマニュアルの close(2) に NFS で quota を使っているときに起こる、という記述があり、これをやってみた。

quota を設定したマシンを NFS server として用意する。NFS client からファイルを open し、たくさん write し、close する。

すると、たしかに close で EDQUOT になった。

で、その失敗した close の後で、fstat すると、EBADF になった。

/proc/self/fd をみても、その fd は close のときになくなる。

つまりこの状況では、close で失敗しても fd は close されているようだ。

#2

NFS client 側で open して書き込んでいるファイルを NFS server 側で rm すると、close 時に ESTALE になるようだ。

quota を使わなくていいところは試すのが簡単だが、quota の例のほうが現実的ではあるかな。

NFS server 側と NFS client 側の両方で同じファイルを定常的にいじくるアプリケーションというのはあまりないと信じたいところだ。

#3

まぁしかし、NFS はなかなか。

2008-12-26 (Fri)

#1

close でエラーになったとき、close されるかもしれないし、されないかもしれない。

では、close されると想定するのと、されないと想定するのと、どちらがよいか。

されると想定してされないと、leak になる。

されないと想定してされると、後で open とかしたときの fd が重なっちゃうと、意図しない操作をしてしまう。

まぁ、あからさまに危険なのは後者かな。

前者については、長期間動作するプログラムではたしかにまずいんだけど、それは状況がちゃんと判明すれば個々に close されない条件を加えていくしかないだろう。

あるいは、race condition を気にしないことにして、close 後に fstat してみるか。

まぁ、まずは具体的な状況を試すのがいいと思うのだが、どうしたものか。EINTR が怪しいんだけど、どうしたら起こせるかなぁ。

2008-12-27 (Sat)

#1

へー、SUS には search.h なんてのがあるのか。2分木とか。

2008-12-31 (Wed)

#1

おぉ、getifaddrs なんていう関数があるのか。

規格にあるわけではないが、BSD/OS, NetBSD, FreeBSD, OpenBSD, GNU/Linux, Mac OS X にはあるようだ。

#2

getifaddrs は動いているマシンのアドレスを得る関数である。

なんでこれが欲しいのかというと、UDP で到着したパケットの宛先がどのアドレスだったか知りたいからである。

それがわからないと返事をそのアドレスから返すことができない。

getifaddrs があれば、INADDR_ANY じゃなくて、個々のアドレスごとにソケットを用意してそれぞれに bind することができる。そうすればどこに到着しても区別できる。

しかし、いろいろと調べていたところ、INADDR_ANY のままでも IP_PKTINFO を使って recvmsg すれば宛先が得られるらしいことに気がついた。

そうすると個々にソケットを作らなくてもいいかも?

いや、返事をするときに送信元を確実に指定するには結局必要になる?

#3

net/http が遅いという話があったが、どうもその原因に関わっていたようだ。

遅い原因はループの中で timeout を使っているという点にあるのだが、timeout を示唆したのは自分だったらしい。検索して出会うまで完全に忘れていたのだが、[ruby-dev:24076] で示唆している。

そして、もっと後に、なにかの飲み会で、バッファを大きくするか IO.select を使うことを青木さんに要求した気がする。

つまり、こうである。

  1. net/protocol.rb は、もともと、タイムアウトを実現するために select を使っていた
  2. 2004-08-14 https で問題が出て、timeout を使うようになった。[ruby-dev:24076]
  3. 2006-07-16 遅いという話が出て read_nonblock と select を使うというのを書いてみた。[ruby-talk:202223]
  4. 2007-03-19 timeout は遅いので、バッファが大きくなった (たしか飲み会での要求の結果だったと思う)
  5. 2008-12-02 Aaron Patterson が read_nonblock と select を使う提案をした。[ruby-core:20191] (変更内容は [ruby-talk:202223] と同じ。)
  6. 2008-12-03 OpenSSL::SSL::SSLSocket には read_nonblock がないので、https で問題が出た。[ruby-dev:37253]
  7. 2008-12-03 Aaron Patterson が OpenSSL::SSL::SSLSocket に read_nonblock を実装 [ruby-core:20241]
  8. 2008-12-03 でもよろしくない [ruby-core:20244] [ruby-core:20246]
  9. 2008-12-04 いくらか修正 [ruby-core:20277]
  10. 2008-12-04 まだよろしくない [ruby-core:20298]
  11. 2008-12-24 rbuf_fill が retry を使うようになる [ruby-cvs:28173]
  12. 2008-12-31 結局自分で直す。[ruby-cvs:28425]

まぁ、経緯も忘れて青木さんに select を使うことを勧めたのはなんとも失敗であった。うぅむ。

さて、select を使わなくなり、そして使うように変わったのだが、最初の問題はどうなったのだろうか。

[ruby-dev:24072] によれば「SSLのレベルでバッファされたデータがある場合に、IO.selectで待っても返ってこない」ということである。なるほどこれはありそうな話である。

これが read_nonblock と select を使う現在のコードで起こるかというと、起こらない。なんでかというと、select を呼び出すのは read_nonblock が EWOULDBLOCK になった後だけで、その状況では SSL のレベルでバッファされたデータは存在しないからである。バッファされたデータが存在したら read_nonblock はそのデータを返して EWOULDBLOCK にはならない。というわけで問題はない。

なお、[ruby-talk:202223] のコードでは select 後に retry せずに read_nonblock を直接呼び出しているが、まつもとさんが [ruby-cvs:28173] で retry に変えている。これは 2008-12-22 に会ったときに、 <URL:2008-12.html#a2008_12_22_2> で書いたようなことを議論したからであろう。


[latest]


田中哲