From cfe8ed5cbc763a3abcfc19ce10619836aeff7682 Mon Sep 17 00:00:00 2001 From: nahi Date: Wed, 31 Aug 2011 08:28:19 +0000 Subject: [PATCH] * Re-apply r33078, thread-safe autoload which is reverted at r33093. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@33146 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 4 + test/ruby/test_autoload.rb | 110 +++++++++++++++++++++ variable.c | 194 +++++++++++++++++++++++++++++++------ vm_insnhelper.c | 3 +- 4 files changed, 279 insertions(+), 32 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4f12f2276b..7d27cbc23a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +Wed Aug 31 17:20:56 2011 Hiroshi Nakamura + + * Re-apply r33078, thread-safe autoload which is reverted at r33093. + Wed Aug 31 16:28:04 2011 NAKAMURA Usaku * test/ruby/test_io_m17n.rb (TestIO_M17N#test_{default_mode_on_dosish, diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index e175e57006..a3a4c5df1a 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -1,4 +1,6 @@ require 'test/unit' +require 'tempfile' +require 'thread' require_relative 'envutil' class TestAutoload < Test::Unit::TestCase @@ -53,4 +55,112 @@ p Foo::Bar assert_equal(tmpfile, b.autoload?(:X), bug4565) } end + + def test_require_explicit + file = Tempfile.open(['autoload', '.rb']) + file.puts 'class Object; AutoloadTest = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + assert(require file.path) + assert_equal(1, ::AutoloadTest) + end + ensure + remove_autoload_constant + end + end + + def test_threaded_accessing_constant + file = Tempfile.open(['autoload', '.rb']) + file.puts 'sleep 0.5; class AutoloadTest; X = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + t1 = Thread.new { ::AutoloadTest::X } + t2 = Thread.new { ::AutoloadTest::X } + [t1, t2].each(&:join) + end + ensure + remove_autoload_constant + end + end + + def test_threaded_accessing_inner_constant + file = Tempfile.open(['autoload', '.rb']) + file.puts 'class AutoloadTest; sleep 0.5; X = 1; end' + file.close + add_autoload(file.path) + begin + assert_nothing_raised do + t1 = Thread.new { ::AutoloadTest::X } + t2 = Thread.new { ::AutoloadTest::X } + [t1, t2].each(&:join) + end + ensure + remove_autoload_constant + end + end + + def test_nameerror_when_autoload_did_not_define_the_constant + file = Tempfile.open(['autoload', '.rb']) + file.puts '' + file.close + add_autoload(file.path) + begin + assert_raise(NameError) do + AutoloadTest + end + ensure + remove_autoload_constant + end + end + + def test_override_autoload + file = Tempfile.open(['autoload', '.rb']) + file.puts '' + file.close + add_autoload(file.path) + begin + eval %q(class AutoloadTest; end) + assert_equal(Class, AutoloadTest.class) + ensure + remove_autoload_constant + end + end + + def test_override_while_autoloading + file = Tempfile.open(['autoload', '.rb']) + file.puts 'class AutoloadTest; sleep 0.5; end' + file.close + add_autoload(file.path) + begin + # while autoloading... + t = Thread.new { AutoloadTest } + sleep 0.1 + # override it + eval %q(AutoloadTest = 1) + t.join + assert_equal(1, AutoloadTest) + ensure + remove_autoload_constant + end + end + + def add_autoload(path) + eval <<-END + class ::Object + autoload :AutoloadTest, #{path.dump} + end + END + end + + def remove_autoload_constant + eval <<-END + class ::Object + remove_const(:AutoloadTest) + end + END + end end diff --git a/variable.c b/variable.c index 6ddbd71390..6b3f814a71 100644 --- a/variable.c +++ b/variable.c @@ -1445,12 +1445,63 @@ static const rb_data_type_t autoload_data_type = { #define check_autoload_table(av) \ (struct st_table *)rb_check_typeddata((av), &autoload_data_type) +static VALUE +autoload_data(VALUE mod, ID id) +{ + struct st_table *tbl; + st_data_t val; + + if (!st_lookup(RCLASS_IV_TBL(mod), autoload, &val) || + !(tbl = check_autoload_table((VALUE)val)) || !st_lookup(tbl, (st_data_t)id, &val)) { + return 0; + } + return (VALUE)val; +} + +struct autoload_data_i { + VALUE feature; + int safe_level; + VALUE thread; + VALUE value; +}; + +static void +autoload_i_mark(void *ptr) +{ + struct autoload_data_i *p = ptr; + rb_gc_mark(p->feature); + rb_gc_mark(p->thread); + rb_gc_mark(p->value); +} + +static void +autoload_i_free(void *ptr) +{ + struct autoload_data_i *p = ptr; + xfree(p); +} + +static size_t +autoload_i_memsize(const void *ptr) +{ + return sizeof(struct autoload_data_i); +} + +static const rb_data_type_t autoload_data_i_type = { + "autoload_i", + {autoload_i_mark, autoload_i_free, autoload_i_memsize,}, +}; + +#define check_autoload_data(av) \ + (struct autoload_data_i *)rb_check_typeddata((av), &autoload_data_i_type) + void rb_autoload(VALUE mod, ID id, const char *file) { st_data_t av; - VALUE fn; + VALUE ad, fn; struct st_table *tbl; + struct autoload_data_i *ele; if (!rb_is_const_id(id)) { rb_raise(rb_eNameError, "autoload must be constant name: %s", rb_id2name(id)); @@ -1473,13 +1524,20 @@ rb_autoload(VALUE mod, ID id, const char *file) st_add_direct(tbl, (st_data_t)autoload, av); DATA_PTR(av) = tbl = st_init_numtable(); } + ad = TypedData_Wrap_Struct(0, &autoload_data_i_type, 0); + st_insert(tbl, (st_data_t)id, (st_data_t)ad); + DATA_PTR(ad) = ele = ALLOC(struct autoload_data_i); + fn = rb_str_new2(file); FL_UNSET(fn, FL_TAINT); OBJ_FREEZE(fn); - st_insert(tbl, (st_data_t)id, (st_data_t)rb_node_newnode(NODE_MEMO, fn, rb_safe_level(), 0)); + ele->feature = fn; + ele->safe_level = rb_safe_level(); + ele->thread = Qnil; + ele->value = Qundef; } -static NODE* +static void autoload_delete(VALUE mod, ID id) { st_data_t val, load = 0, n = id; @@ -1498,8 +1556,6 @@ autoload_delete(VALUE mod, ID id) st_delete(RCLASS_IV_TBL(mod), &n, &val); } } - - return (NODE *)load; } static VALUE @@ -1516,22 +1572,18 @@ reset_safe(VALUE safe) return safe; } -static NODE * -autoload_node(VALUE mod, ID id, const char **loadingpath) +static VALUE +check_autoload_required(VALUE mod, ID id, const char **loadingpath) { - VALUE file; - struct st_table *tbl; - st_data_t val; - NODE *load; + VALUE file, load; + struct autoload_data_i *ele; const char *loading; int safe; - if (!st_lookup(RCLASS_IV_TBL(mod), autoload, &val) || - !(tbl = check_autoload_table((VALUE)val)) || !st_lookup(tbl, (st_data_t)id, &val)) { + if (!(load = autoload_data(mod, id)) || !(ele = check_autoload_data(load))) { return 0; } - load = (NODE *)val; - file = load->nd_lit; + file = ele->feature; Check_Type(file, T_STRING); if (!RSTRING_PTR(file) || !*RSTRING_PTR(file)) { rb_raise(rb_eArgError, "empty file name"); @@ -1550,7 +1602,7 @@ autoload_node(VALUE mod, ID id, const char **loadingpath) } static int -autoload_node_id(VALUE mod, ID id) +autoload_defined_p(VALUE mod, ID id) { struct st_table *tbl = RCLASS_CONST_TBL(mod); st_data_t val; @@ -1561,42 +1613,109 @@ autoload_node_id(VALUE mod, ID id) return 1; } +int +rb_autoloading_value(VALUE mod, ID id, VALUE* value) +{ + VALUE load; + struct autoload_data_i *ele; + + if (!(load = autoload_data(mod, id)) || !(ele = check_autoload_data(load))) { + return 0; + } + if (ele->thread == rb_thread_current()) { + if (ele->value != Qundef) { + if (value) { + *value = ele->value; + } + return 1; + } + } + return 0; +} + +struct autoload_const_set_args { + VALUE mod; + ID id; + VALUE value; +}; + +static void +autoload_const_set(struct autoload_const_set_args* args) +{ + autoload_delete(args->mod, args->id); + rb_const_set(args->mod, args->id, args->value); +} + +static VALUE +autoload_require(struct autoload_data_i *ele) +{ + return rb_require_safe(ele->feature, ele->safe_level); +} + VALUE rb_autoload_load(VALUE mod, ID id) { - VALUE file; - NODE *load; + VALUE load, result; const char *loading = 0, *src; + struct autoload_data_i *ele; + int state = 0; - if (!autoload_node_id(mod, id)) return Qfalse; - load = autoload_node(mod, id, &loading); + if (!autoload_defined_p(mod, id)) return Qfalse; + load = check_autoload_required(mod, id, &loading); if (!load) return Qfalse; src = rb_sourcefile(); if (src && loading && strcmp(src, loading) == 0) return Qfalse; - file = load->nd_lit; - return rb_require_safe(file, (int)load->nd_nth); + + /* set ele->thread for a marker of autoloading thread */ + if (!(ele = check_autoload_data(load))) { + return Qfalse; + } + if (ele->thread == Qnil) { + ele->thread = rb_thread_current(); + } + /* autoload_data_i can be deleted by another thread while require */ + RB_GC_GUARD(load); + result = rb_protect((VALUE(*)(VALUE))autoload_require, (VALUE)ele, &state); + if (ele->thread == rb_thread_current()) { + ele->thread = Qnil; + } + if (state) rb_jump_tag(state); + + if (RTEST(result)) { + /* At the last, move a value defined in autoload to constant table */ + if (ele->value != Qundef) { + int safe_backup; + struct autoload_const_set_args args; + args.mod = mod; + args.id = id; + args.value = ele->value; + safe_backup = rb_safe_level(); + rb_set_safe_level_force(ele->safe_level); + rb_ensure((VALUE(*)(VALUE))autoload_const_set, (VALUE)&args, reset_safe, (VALUE)safe_backup); + } + } + return result; } VALUE rb_autoload_p(VALUE mod, ID id) { - VALUE file; - NODE *load; - const char *loading = 0; + VALUE load; + struct autoload_data_i *ele; - while (!autoload_node_id(mod, id)) { + while (!autoload_defined_p(mod, id)) { mod = RCLASS_SUPER(mod); if (!mod) return Qnil; } - load = autoload_node(mod, id, &loading); + load = check_autoload_required(mod, id, 0); if (!load) return Qnil; - return load && (file = load->nd_lit) ? file : Qnil; + return (ele = check_autoload_data(load)) ? ele->feature : Qnil; } static VALUE rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) { - VALUE value, tmp; + VALUE value, tmp, av; int mod_retry = 0; tmp = klass; @@ -1613,6 +1732,7 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility) if (value == Qundef) { if (am == tmp) break; am = tmp; + if (rb_autoloading_value(tmp, id, &av)) return av; rb_autoload_load(tmp, id); continue; } @@ -1843,7 +1963,7 @@ rb_const_defined_0(VALUE klass, ID id, int exclude, int recurse, int visibility) if (visibility && ce->flag == CONST_PRIVATE) { return (int)Qfalse; } - if (ce->value == Qundef && !autoload_node(tmp, id, 0)) + if (ce->value == Qundef && !check_autoload_required(tmp, id, 0) && !rb_autoloading_value(tmp, id, 0)) return (int)Qfalse; return (int)Qtrue; } @@ -1922,8 +2042,20 @@ rb_const_set(VALUE klass, ID id, VALUE val) if (st_lookup(RCLASS_CONST_TBL(klass), (st_data_t)id, &value)) { rb_const_entry_t *ce = (rb_const_entry_t*)value; - if (ce->value == Qundef) + if (ce->value == Qundef) { + VALUE load; + struct autoload_data_i *ele; + + load = autoload_data(klass, id); + /* for autoloading thread, keep the defined value to autoloading storage */ + if (load && (ele = check_autoload_data(load)) && (ele->thread == rb_thread_current())) { + rb_vm_change_state(); + ele->value = val; + return; + } + /* otherwise, allow to override */ autoload_delete(klass, id); + } else { visibility = ce->flag; rb_warn("already initialized constant %s", rb_id2name(id)); diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 9410963d5c..e79d4a092d 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1178,7 +1178,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, cref = cref->nd_next; if (!NIL_P(klass)) { - VALUE am = 0; + VALUE av, am = 0; st_data_t data; search_continue: if (RCLASS_CONST_TBL(klass) && @@ -1188,6 +1188,7 @@ vm_get_ev_const(rb_thread_t *th, const rb_iseq_t *iseq, if (am == klass) break; am = klass; if (is_defined) return 1; + if (rb_autoloading_value(klass, id, &av)) return av; rb_autoload_load(klass, id); goto search_continue; }