[#7102] Ruby 1.3.4-990611 — Yukihiro Matsumoto <matz@...>

Ruby 1.3.4-990611 is out, check out:

20 messages 1999/06/11

[#7223] Ruby 1.3.4-990625 — Yukihiro Matsumoto <matz@...>

Ruby 1.3.4-990625 is out, check out:

14 messages 1999/06/25
[#7224] -Wl,-rpath on Linux (Re: Ruby 1.3.4-990625) — Ryo HAYASAKA <hayasaka@...21.u-aizu.ac.jp> 1999/06/25

早坂@会津大学です。

[ruby-dev:7159] Re: Ruby 1.3.4-990611

From: matz@... (Yukihiro Matsumoto)
Date: 1999-06-22 01:15:40 UTC
List: ruby-dev #7159
まつもと ゆきひろです

In message "[ruby-dev:7109] Re: Ruby 1.3.4-990611"
    on 99/06/14, WATANABE Tetsuya <tetsu@jpn.hp.com> 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 <sys/select.h>
 #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);


In This Thread