ついに気が向いたので、以前から考えていた、stdio のバッファを考慮する
sysread こと readpartial を実装してみました。

こういうメソッドがあると、パイプやソケットからどのくらいデータ到着して
いるか分からないんだけど到着しているデータはまとまった単位で取り込みた
い、という要求を nonblock や sysread を使わずに満たすことが出来ます。

ここで、nonblock を使いたくないのは、nonblock はトラブルの元だからです。
また、sysread を使いたくないのは、IO の他の (stdio のバッファを扱う)
メソッドを使えなくなるからです。

readpartial があれば、IO の他のメソッドを使うことを許容しつつ、
nonblock を避けることが出来ます。

というわけで、こういうのをつけるのはどうでしょう?

test/ruby/test_readpartial.rb:

require 'test/unit'
require 'timeout'
require 'fcntl'

class TestReadPartial < Test::Unit::TestCase
  def make_pipe
    r, w = IO.pipe
    begin
      yield r, w
    ensure
      r.close unless r.closed?
      w.close unless w.closed?
    end
  end

  def pipe
    make_pipe {|r, w|
      yield r, w
    }
    make_pipe {|r, w|
      r.fcntl(Fcntl::F_SETFL, r.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
      yield r, w
    }
  end

  def test_closed_pipe
    pipe {|r, w|
      w << 'abc'
      w.close
      assert_equal('ab', r.readpartial(2))
      assert_equal('c', r.readpartial(2))
      assert_equal(nil, r.readpartial(2))
      assert_equal(nil, r.readpartial(2))
    }
  end

  def test_open_pipe
    pipe {|r, w|
      w << 'abc'
      assert_equal('ab', r.readpartial(2))
      assert_equal('c', r.readpartial(2))
      assert_raises(TimeoutError) {
        timeout(0.1) { r.readpartial(2) }
      }
    }
  end

  def test_with_stdio
    pipe {|r, w|
      w << "abc\ndef\n"
      assert_equal("abc\n", r.gets)
      w << "ghi\n"
      assert_equal("de", r.readpartial(2))
      assert_equal("f\n", r.readpartial(4096))
      assert_equal("ghi\n", r.readpartial(4096))
      assert_raises(TimeoutError) {
        timeout(0.1) { r.readpartial(2) }
      }
    }
  end

end

なお、実装はまだ試験的なもので、IO にしか実装してません。また、効率の
点からは可能なら READ_DATA_PENDING_COUNT を使うべきですが、まだそうし
ていません。

Index: io.c
===================================================================
RCS file: /src/ruby/io.c,v
retrieving revision 1.265
diff -u -r1.265 io.c
--- io.c	19 Mar 2004 07:12:52 -0000	1.265
+++ io.c	28 Mar 2004 04:42:17 -0000
@@ -1024,6 +1024,86 @@
 
 /*
  *  call-seq:
+ *     ios.readpartial(integer [, buffer])    => string, buffer, or nil
+ *  
+ *  Reads at most <i>integer</i> bytes from the I/O stream but
+ *  it blocks only if <em>ios</em> has no data immediately available.
+ *  If the optional <i>buffer</i> argument is present,
+ *  it must reference a String, which will receive the data.
+ *  Returns <code>nil</code> if called at end of file.
+ *     
+ *     STDIN.readpartial(4096)   #=> "Data immediately available"
+ */
+
+static VALUE
+io_readpartial(argc, argv, io)
+    int argc;
+    VALUE *argv;
+    VALUE io;
+{
+    OpenFile *fptr;
+    long n, len;
+    VALUE length, str;
+
+    rb_scan_args(argc, argv, "11", &length, &str);
+
+    GetOpenFile(io, fptr);
+    rb_io_check_readable(fptr);
+
+    len = NUM2LONG(length);
+    if (len < 0) {
+	rb_raise(rb_eArgError, "negative length %ld given", len);
+    }
+
+    if (NIL_P(str)) {
+	str = rb_str_new(0, len);
+    }
+    else {
+	StringValue(str);
+	rb_str_modify(str);
+	rb_str_resize(str,len);
+    }
+    if (len == 0) return str;
+
+    READ_CHECK(fptr->f);
+    {
+        char *ptr = RSTRING(str)->ptr;
+        if (READ_DATA_PENDING(fptr->f)) {
+            for (n = 0; n < len && READ_DATA_PENDING(fptr->f); n++) {
+                int c;
+                TRAP_BEG;
+                c = getc(fptr->f);
+                TRAP_END;
+                *ptr++ = c;
+            }
+        }
+        else {
+            for (;;) {
+                TRAP_BEG;
+                n = read(fileno(fptr->f), ptr, len);
+                TRAP_END;
+                if (n != -1)
+                    break;
+                if (!rb_io_wait_readable(fileno(fptr->f))) {
+                    rb_str_resize(str, 0);
+                    rb_sys_fail(fptr->path);
+                }
+            }
+        }
+    }
+    if (n == 0) {
+	rb_str_resize(str, 0);
+	return Qnil;
+    }
+    RSTRING(str)->len = n;
+    RSTRING(str)->ptr[n] = '\0';
+    OBJ_TAINT(str);
+
+    return str;
+}
+
+/*
+ *  call-seq:
  *     ios.read([integer [, buffer]])    => string, buffer, or nil
  *  
  *  Reads at most <i>integer</i> bytes from the I/O stream, or to the
@@ -5395,6 +5475,7 @@
 
     rb_define_method(rb_cIO, "readlines",  rb_io_readlines, -1);
 
+    rb_define_method(rb_cIO, "readpartial",  io_readpartial, -1);
     rb_define_method(rb_cIO, "read",  io_read, -1);
     rb_define_method(rb_cIO, "write", io_write, 1);
     rb_define_method(rb_cIO, "gets",  rb_io_gets_m, -1);
-- 
[田中 哲][たなか あきら][Tanaka Akira]