ついに気が向いたので、以前から考えていた、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]