From 6cefcff5889cceaa001f76f4be1a1c5e513b241d Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Fri, 13 May 2011 15:55:50 -0700 Subject: add Kgio.tryopen method For the case where a file is readable, it's faster to just call open() instead of stat()-ing and then calling open(). open() failures are still relatively cheap, too, they're still more expensive than a failed stat() but cheaper than raising an exception. --- ext/kgio/kgio.h | 1 + ext/kgio/kgio_ext.c | 1 + ext/kgio/tryopen.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_tryopen.rb | 71 +++++++++++++++++++++++++++ 4 files changed, 207 insertions(+) create mode 100644 ext/kgio/tryopen.c create mode 100644 test/test_tryopen.rb diff --git a/ext/kgio/kgio.h b/ext/kgio/kgio.h index 9056cef..0ecfde0 100644 --- a/ext/kgio/kgio.h +++ b/ext/kgio/kgio.h @@ -36,6 +36,7 @@ void init_kgio_accept(void); void init_kgio_connect(void); void init_kgio_autopush(void); void init_kgio_poll(void); +void init_kgio_tryopen(void); void kgio_autopush_accept(VALUE, VALUE); void kgio_autopush_recv(VALUE); diff --git a/ext/kgio/kgio_ext.c b/ext/kgio/kgio_ext.c index f50b3c8..2365fd0 100644 --- a/ext/kgio/kgio_ext.c +++ b/ext/kgio/kgio_ext.c @@ -8,4 +8,5 @@ void Init_kgio_ext(void) init_kgio_accept(); init_kgio_autopush(); init_kgio_poll(); + init_kgio_tryopen(); } diff --git a/ext/kgio/tryopen.c b/ext/kgio/tryopen.c new file mode 100644 index 0000000..c9d7bb6 --- /dev/null +++ b/ext/kgio/tryopen.c @@ -0,0 +1,134 @@ +#include +#ifdef HAVE_RUBY_IO_H +# include +#else +# include +#endif + +#ifdef HAVE_RUBY_ST_H +# include +#else +# include +#endif + +#include +#include +#include + +static ID id_for_fd, id_to_path; +static st_table *errno2sym; + +struct open_args { + const char *pathname; + int flags; + mode_t mode; +}; + +static VALUE nogvl_open(void *ptr) +{ + struct open_args *o = ptr; + + return (VALUE)open(o->pathname, o->flags, o->mode); +} + +#ifndef HAVE_RB_THREAD_BLOCKING_REGION +# define RUBY_UBF_IO (-1) +typedef void rb_unblock_function_t(void *); +typedef VALUE rb_blocking_function_t(void *); +static VALUE rb_thread_blocking_region( + rb_blocking_function_t *fn, void *data1, + rb_unblock_function_t *ubf, void *data2) +{ + TRAP_BEG; /* for FIFO */ + rv = fn(data1); + TRAP_END; + + return rv; +} +#endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */ + +static VALUE s_tryopen(int argc, VALUE *argv, VALUE klass) +{ + int fd; + VALUE pathname, flags, mode; + struct open_args o; + int retried = 0; + + rb_scan_args(argc, argv, "12", &pathname, &flags, &mode); + o.pathname = StringValueCStr(pathname); + + switch (TYPE(flags)) { + case T_NIL: o.flags = O_RDONLY; break; + case T_FIXNUM: o.flags = FIX2INT(flags); break; + case T_BIGNUM: o.flags = NUM2INT(flags); break; + default: rb_raise(rb_eArgError, "flags must be an Integer"); + } + switch (TYPE(mode)) { + case T_NIL: o.mode = 0666; break; + case T_FIXNUM: o.mode = FIX2INT(mode); break; + case T_BIGNUM: o.mode = NUM2INT(mode); break; + default: rb_raise(rb_eArgError, "mode must be an Integer"); + } + +retry: + fd = (int)rb_thread_blocking_region(nogvl_open, &o, RUBY_UBF_IO, 0); + if (fd == -1) { + if (errno == EMFILE || errno == ENFILE) { + rb_gc(); + if (retried) + rb_sys_fail(o.pathname); + retried = 1; + goto retry; + } + if (fd == -1) { + int saved_errno = errno; + VALUE rv; + + if (!st_lookup(errno2sym, (st_data_t)errno, &rv)) { + errno = saved_errno; + rb_sys_fail(o.pathname); + } + return rv; + } + } + return rb_funcall(rb_cFile, id_for_fd, 1, INT2FIX(fd)); +} + +void init_kgio_tryopen(void) +{ + VALUE mKgio = rb_define_module("Kgio"); + VALUE tmp; + VALUE *ptr; + long len; + + rb_define_singleton_method(mKgio, "tryopen", s_tryopen, -1); + id_for_fd = rb_intern("for_fd"); + id_to_path = rb_intern("to_path"); + + errno2sym = st_init_numtable(); + tmp = rb_funcall(rb_mErrno, rb_intern("constants"), 0); + ptr = RARRAY_PTR(tmp); + len = RARRAY_LEN(tmp); + for (; --len >= 0; ptr++) { + VALUE error; + ID const_id; + + switch (TYPE(*ptr)) { + case T_SYMBOL: const_id = SYM2ID(*ptr); break; + case T_STRING: const_id = rb_intern(RSTRING_PTR(*ptr)); break; + default: rb_bug("constant not a symbol or string"); + } + + error = rb_const_get(rb_mErrno, const_id); + if (!rb_const_defined(error, rb_intern("Errno"))) + continue; + + error = rb_const_get(error, rb_intern("Errno")); + switch (TYPE(error)) { + case T_FIXNUM: + case T_BIGNUM: + st_insert(errno2sym, (st_data_t)NUM2INT(error), + ID2SYM(const_id)); + } + } +} diff --git a/test/test_tryopen.rb b/test/test_tryopen.rb new file mode 100644 index 0000000..c885ca0 --- /dev/null +++ b/test/test_tryopen.rb @@ -0,0 +1,71 @@ +require 'tempfile' +require 'test/unit' +$-w = true +require 'kgio' + +class TestTryopen < Test::Unit::TestCase + + def test_tryopen_success + tmp = Kgio.tryopen(__FILE__) + assert_instance_of File, tmp + assert_equal File.read(__FILE__), tmp.read + assert_nothing_raised { tmp.close } + end + + def test_tryopen_ENOENT + tmp = Tempfile.new "tryopen" + path = tmp.path + tmp.close! + tmp = Kgio.tryopen(path) + assert_equal :ENOENT, tmp + end + + def test_tryopen_EPERM + tmp = Tempfile.new "tryopen" + File.chmod 0000, tmp.path + tmp = Kgio.tryopen(tmp.path) + assert_equal :EACCES, tmp + end + + def test_tryopen_readwrite + tmp = Tempfile.new "tryopen" + file = Kgio.tryopen(tmp.path, IO::RDWR) + file.syswrite "FOO" + assert_equal "FOO", tmp.sysread(3) + end + + def test_tryopen_mode + tmp = Tempfile.new "tryopen" + path = tmp.path + tmp.close! + file = Kgio.tryopen(path, IO::RDWR|IO::CREAT, 0000) + assert_equal 0100000, File.stat(path).mode + ensure + File.unlink path + end + + require "benchmark" + def test_benchmark + nr = 1000000 + tmp = Tempfile.new('tryopen') + file = tmp.path + Benchmark.bmbm do |x| + x.report("tryopen (OK)") do + nr.times { Kgio.tryopen(file).close } + end + x.report("open (OK)") do + nr.times { File.readable?(file) && File.open(file).close } + end + end + tmp.close! + assert_equal :ENOENT, Kgio.tryopen(file) + Benchmark.bmbm do |x| + x.report("tryopen (ENOENT)") do + nr.times { Kgio.tryopen(file) } + end + x.report("open (ENOENT)") do + nr.times { File.readable?(file) && File.open(file) } + end + end + end if ENV["BENCHMARK"] +end -- cgit v1.2.3-24-ge0c7