From: matz@... (Yukihiro Matsumoto) Date: 1999-06-22T10:15:40+09:00 Subject: [ruby-dev:7159] Re: Ruby 1.3.4-990611 まつもと ゆきひろです In message "[ruby-dev:7109] Re: Ruby 1.3.4-990611" on 99/06/14, WATANABE Tetsuya writes: |動きが変わったのでお知らせします。 どうもです。 |$stdout = IO.popen('nkf -j', 'w') | |のように $stdout を上書きすると、出力時にプロセスがブロック |してしまうことが起きました。 なかなか謎ですね。 |io.c 1495 に |rb_obj_call_init((VALUE)port, 0, 0); で解決してしまうところなんか(このメソッドは実質なにもしませ ん)、ミステリです。正直言うとかなり悩みました。 しかし、これくらい詳細な情報を頂けると大体の謎は解けるもので す。結論から言うと、「謎は全て解けたっ」です。 最初のヒントはこれです。 |GC との関係がありそうで、スクリプトから不要? な部分を削ると |起きません。 GCは確かに怪しいですね。そして決めてはスタックトレースです。 これはいつも重要ですね。 |■ 止っているときの状況 | |gdb 上で再現して、止っているところを backtrace しました。 |#3 0x8078b6f in rb_syswait () |#6 0x80642f9 in rb_io_fptr_finalize () |#8 0x8060494 in rb_gc_mark () |#9 0x8060a36 in rb_gc () なんか変なシンボル混じってますが、重要なのはこれらのシンボル です。これはGCでIOが回収されて、その中でrb_syswait()が呼ばれ ていることを表しています。 そこで調べてみると $stdin = open("|..", "r") のようなタイプではreopen()が使われています。つまり、元々の $stdinにfdがdupされているわけです。 もうわかりましたね。つまり、open()によって生成されたpopenオ ブジェクトはdupされた後、そのままゴミとなり、まだ終了もして いないのにGCの中でwait4(2)されていたわけです。これではインタ プリタ全体が停止してしまいます。 そこで、まずGC中ではwaitしないようにします。これでまず停止す ると言う問題は解決します。 --- io.c~ Mon Jun 21 23:01:27 1999 +++ io.c Mon Jun 21 22:42:40 1999 @@ -884,8 +884,4 @@ fclose(fptr->f2); } - if (fptr->pid) { - rb_syswait(fptr->pid); - fptr->pid = 0; - } } @@ -904,4 +900,8 @@ else { fptr_finalize(fptr); + if (fptr->pid) { + rb_syswait(fptr->pid); + fptr->pid = 0; + } } fptr->f = fptr->f2 = NULL; ただし、これで全ての問題が解決したかと言うとそうでもありませ ん。以下の問題が気になります。 * dupしていないpopenオブジェクトがゴミになると、ゾンビが残っ てしまう * $std{in,out,err}への代入がreopenによって行われるのは直観 に反する。保存のために直前にdupを必要とするし 前者はまあ無視することにします。dupする時にカウンタを更新す ることなど、やり方はないわけではないのですが、とりあえず気持 ちの問題に過ぎないので、これは将来への課題として残しておくこ とにしましょう。 後者は昔から嫌だなと思っていたのでした。そこで、以下のような ことをしてみることにしました。手元では動いているようですが、 どうでしょう。 これは内部的に実質reopenしているのですが、「$stdinの値を固定」 という以前のやや無理のある(他の代入モデルと違う)挙動を緩和し てみました。これでdupは不要になります。 --- io.c~ Mon Jun 21 23:01:27 1999 +++ io.c Mon Jun 21 23:15:58 1999 @@ -2032,6 +2032,25 @@ } +static int +rb_dup(orig) + int orig; +{ + int fd; + + fd = dup(orig); + if (fd < 0) { + if (errno == EMFILE || errno == ENFILE) { + rb_gc(); + fd = dup(orig); + } + if (fd < 0) { + rb_sys_fail(0); + } + } + return fd; +} + static void -rb_io_stdio_set(val, id, var) +set_stdin(val, id, var) VALUE val; ID id; @@ -2040,21 +2059,89 @@ OpenFile *fptr; int fd; + char *mode; + if (val == *var) return; if (TYPE(val) != T_FILE) { rb_raise(rb_eTypeError, "%s must be IO object", rb_id2name(id)); } - if (ruby_verbose) { - rb_warn("assignment for %s is done by reopen", rb_id2name(id)); - } - GetOpenFile(*var, fptr); - fd = fileno(fptr->f); + GetOpenFile(val, fptr); - if (fd == 0) { rb_io_check_readable(fptr); + + GetOpenFile(*var, fptr); + mode = rb_io_mode_string(fptr); + fd = rb_dup(fileno(fptr->f)); + if (fileno(fptr->f) > 2) { + fclose(fptr->f); } - else { + fptr->f = rb_fdopen(fd, mode); + + GetOpenFile(val, fptr); + dup2(fileno(fptr->f), 0); + fclose(fptr->f); + fptr->f = stdin; + + *var = val; +} + +static void +set_outfile(val, id, var, stdf) + VALUE val; + ID id; + VALUE *var; + FILE *stdf; +{ + OpenFile *fptr; + FILE *f; + int fd; + char *mode; + + if (val == *var) return; + rb_io_flush(*var); + + if (TYPE(val) != T_FILE) { + rb_raise(rb_eTypeError, "%s must be IO object", rb_id2name(id)); + } + + GetOpenFile(val, fptr); rb_io_check_writable(fptr); + + GetOpenFile(*var, fptr); + mode = rb_io_mode_string(fptr); + f = GetWriteFile(fptr); + fd = rb_dup(fileno(f)); + if (fileno(f) > 2) { + fclose(fptr->f); + } + f = rb_fdopen(fd, mode); + if (fptr->f2) fptr->f2 = f; + else fptr->f = f; + + GetOpenFile(val, fptr); + f = GetWriteFile(fptr); + dup2(fileno(f), fileno(stdf)); + fclose(f); + if (fptr->f2) fptr->f2 = stdf; + else fptr->f = stdf; + + *var = val; } - rb_io_reopen(*var, val); + +static void +set_stdout(val, id, var) + VALUE val; + ID id; + VALUE *var; +{ + set_outfile(val, id, var, stdout); +} + +static void +set_stderr(val, id, var) + VALUE val; + ID id; + VALUE *var; +{ + set_outfile(val, id, var, stderr); } @@ -2331,7 +2418,4 @@ #include #endif -#ifdef NT -#define select(v, w, x, y, z) (-1) /* anytime fail */ -#endif static VALUE @@ -3100,9 +3184,9 @@ rb_stdin = prep_stdio(stdin, FMODE_READABLE, rb_cIO); - rb_define_hooked_variable("$stdin", &rb_stdin, 0, rb_io_stdio_set); + rb_define_hooked_variable("$stdin", &rb_stdin, 0, set_stdin); rb_stdout = prep_stdio(stdout, FMODE_WRITABLE, rb_cIO); - rb_define_hooked_variable("$stdout", &rb_stdout, 0, rb_io_stdio_set); + rb_define_hooked_variable("$stdout", &rb_stdout, 0, set_stdout); rb_stderr = prep_stdio(stderr, FMODE_WRITABLE, rb_cIO); - rb_define_hooked_variable("$stderr", &rb_stderr, 0, rb_io_stdio_set); + rb_define_hooked_variable("$stderr", &rb_stderr, 0, set_stderr); rb_defout = rb_stdout; rb_define_hooked_variable("$>", &rb_defout, 0, rb_io_defset);