pipe
rubyコマンドを実行し、pipeを利用して実行結果*1を受け取ろうとしたら、なぜかパーサがブロックしてしまい残念な結果になってしまっていた。もういやになってうっちゃっていたが、このままではTyping Rubyの開発が一歩も進まないので*2、落ち着いて調べてみた。
結論だけ言えば、Unix.create_processは標準出力として与えられたfile_descrをUnix.closeしてくれないので、与えた標準出力の反対から読むようにしているとそこでブロックしてしまうことが原因だった。どうやるのが定石なのかはちょっとわからないが、とりあえずUnix.wait_pidしてUnix.closeしてやる関数を作って別スレッドで待たせてやれば問題は解決する。
まず、単純にpipeとして動作するプログラムを書いてみる。
let simply_pipe () = let args = Array.of_list ["cat"; "foo.ml"] in let pid = Unix.create_process "cat" args Unix.stdin Unix.stdout Unix.stderr in Unix.waitpid [] pid let _ = simply_pipe()
このプログラムはうまく動作した。
そこで、barに与えたチャンネルから1行ずつ読んで出力するプログラムを作ってみる。
let like_ruby () = let args = Array.of_list ["cat"; "foo.ml"] in let (out_in, out) = Unix.pipe() in let pid = Unix.create_process "cat" args Unix.stdin out Unix.stderr in try let inchan = Unix.in_channel_of_descr out_in in while true do let s = input_line inchan in Printf.printf "# %s\n" s; flush stdout done with End_of_file -> (); Unix.wait_pid [] pid let _ = like_ruby()
こんな感じ。これは見事にブロックしてくれる。目論見どおりである。どこでブロックしているのかをデバッガで追うと、let s = input_line inchan inでブロックしていた。そこで、Unix.wait_pid [] pidをtryの前に持っていっても症状は同じ。
outがクローズされていないことが原因なのだから、Unix.close outをどこかに入れてやればいいことになる。とりあえずtryの前に入れて、うまく動作することが確認できた。
ここまでくれば後は簡単。別スレッドで待ってファイルを閉じてやる関数を動かせばそれでいいはず。
let wait_and_close (pid,out) = Unix.waitpid [] pid; Unix.close out let like_ruby () = let args = Array.of_list ["cat"; "foo.ml"] in let (out_in, out) = Unix.pipe() in let pid = Unix.create_process "cat" args Unix.stdin out Unix.stderr in let _ = Thread.create wait_and_close (pid,out) in let inchan = Unix.in_channel_of_descr out_in in try while true do let s = input_line inchan in Printf.printf "# %s\n" s; flush stdout done with End_of_file -> () let _ = like_ruby()
として問題は解決。Thread周りがちょっと不安だけど、まあ大丈夫でしょう。動いてるし。