From: Tanaka Akira Date: 2004-03-28T14:02:08+09:00 Subject: [ruby-dev:23247] IO#readpartial ついに気が向いたので、以前から考えていた、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 integer bytes from the I/O stream but + * it blocks only if ios has no data immediately available. + * If the optional buffer argument is present, + * it must reference a String, which will receive the data. + * Returns nil 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 integer 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]