From f2474394d92daa07ed05dd07dd906e4c6cf2be95 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 27 Jul 2015 03:02:28 +0930 Subject: [PATCH] * variable.c: allow autoload to use a proc instead of a filename. Autoload is only really useful if you're loading new code from a file... but this change allows the autoload-registering caller to wrap any file-loading as they see fit. This allows them to implement their own context-aware locking/thread- safety mechanism, or to register nested autoloads after the current constant is created. It was previously only possible to achieve the same result, while still using autoload, by writing a tempfile for each autoload entry. * load.c: expose new autoload-proc support. * intern.h: ditto. --- include/ruby/intern.h | 1 + load.c | 8 +++++-- test/ruby/test_autoload.rb | 52 ++++++++++++++++++++++++++++++++++++++++++++++ variable.c | 38 +++++++++++++++++++++++++-------- 4 files changed, 88 insertions(+), 11 deletions(-) diff --git a/include/ruby/intern.h b/include/ruby/intern.h index 56865f3..5971143 100644 --- a/include/ruby/intern.h +++ b/include/ruby/intern.h @@ -935,6 +935,7 @@ VALUE rb_path2class(const char*); void rb_name_class(VALUE, ID); VALUE rb_class_name(VALUE); void rb_autoload(VALUE, ID, const char*); +void rb_autoload_value(VALUE, ID, VALUE); VALUE rb_autoload_load(VALUE, ID); VALUE rb_autoload_p(VALUE, ID); VALUE rb_f_trace_var(int, const VALUE*); diff --git a/load.c b/load.c index fbe7591..1db9083 100644 --- a/load.c +++ b/load.c @@ -1123,8 +1123,12 @@ rb_mod_autoload(VALUE mod, VALUE sym, VALUE file) { ID id = rb_to_id(sym); - FilePathValue(file); - rb_autoload(mod, id, RSTRING_PTR(file)); + if (rb_obj_is_proc(file)) { + rb_autoload_value(mod, id, file); + } else { + FilePathValue(file); + rb_autoload(mod, id, RSTRING_PTR(file)); + } return Qnil; } diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index ed73138..83d087a 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -212,6 +212,58 @@ def test_require_implemented_in_ruby_is_called Kernel.module_eval do; alias :require :old_require; undef :old_require; end end + def test_simple_proc_invocation + calls = 0 + add_autoload -> { + calls += 1 + ::Object.const_set(:AutoloadTest, 999) + 0 + } + assert_equal(999, Object::AutoloadTest) + assert_equal(999, Object::AutoloadTest) + assert_equal(1, calls) + ensure + remove_autoload_constant + end + + def test_concurrent_proc_invocation + require 'monitor' + calls = 0 + n = 0 + m = Monitor.new + add_autoload -> { + calls += 1 + sleep 0.1 until calls >= 2 + m.synchronize { + if ::Object.autoload?(:AutoloadTest) + ::Object.const_set :AutoloadTest, "x#{n += 1}" + end + } + } + t1 = Thread.new { Object::AutoloadTest } + t2 = Thread.new { Object::AutoloadTest } + assert_equal 'x1', t1.value + assert_equal 'x1', t2.value + assert_equal(2, calls) + assert_equal(1, n) + ensure + remove_autoload_constant + end + + def test_proc_with_class_definition + add_autoload -> { + Tempfile.create(['autoload', '.rb']) {|file| + file.puts 'class AutoloadTest; end' + file.close + @autoload_paths << file.path + require file.path + } + } + assert(Object::AutoloadTest) + ensure + remove_autoload_constant + end + def add_autoload(path) (@autoload_paths ||= []) << path ::Object.class_eval {autoload(:AutoloadTest, path)} diff --git a/variable.c b/variable.c index bb8b6db..87ee0bd 100644 --- a/variable.c +++ b/variable.c @@ -1909,8 +1909,23 @@ static const rb_data_type_t autoload_data_i_type = { void rb_autoload(VALUE mod, ID id, const char *file) { + VALUE fn; + + if (!file || !*file) { + rb_raise(rb_eArgError, "empty file name"); + } + fn = rb_str_new2(file); + FL_UNSET(fn, FL_TAINT); + OBJ_FREEZE(fn); + + rb_autoload_value(mod, id, fn); +} + +void +rb_autoload_value(VALUE mod, ID id, VALUE source) +{ st_data_t av; - VALUE ad, fn; + VALUE ad; struct st_table *tbl; struct autoload_data_i *ele; rb_const_entry_t *ce; @@ -1919,9 +1934,6 @@ rb_autoload(VALUE mod, ID id, const char *file) rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(id)); } - if (!file || !*file) { - rb_raise(rb_eArgError, "empty file name"); - } ce = rb_const_lookup(mod, id); if (ce && ce->value != Qundef) { @@ -1940,12 +1952,9 @@ rb_autoload(VALUE mod, ID id, const char *file) RB_OBJ_WRITTEN(mod, Qnil, av); DATA_PTR(av) = tbl = st_init_numtable(); } - fn = rb_str_new2(file); - FL_UNSET(fn, FL_TAINT); - OBJ_FREEZE(fn); ad = TypedData_Make_Struct(0, struct autoload_data_i, &autoload_data_i_type, ele); - ele->feature = fn; + ele->feature = source; ele->safe_level = rb_safe_level(); ele->thread = Qnil; ele->value = Qundef; @@ -1995,6 +2004,13 @@ check_autoload_required(VALUE mod, ID id, const char **loadingpath) return 0; } file = ele->feature; + if (rb_obj_is_proc(file)) { + if (ele->thread == rb_thread_current()) { + return 0; + } else { + return load; + } + } Check_Type(file, T_STRING); if (!RSTRING_PTR(file) || !*RSTRING_PTR(file)) { rb_raise(rb_eArgError, "empty file name"); @@ -2064,7 +2080,11 @@ static VALUE autoload_require(VALUE arg) { struct autoload_data_i *ele = (struct autoload_data_i *)arg; - return rb_funcall(rb_vm_top_self(), rb_intern("require"), 1, ele->feature); + if (rb_obj_is_proc(ele->feature)) { + return rb_funcall(ele->feature, rb_intern("call"), 0); + } else { + return rb_funcall(rb_vm_top_self(), rb_intern("require"), 1, ele->feature); + } } static VALUE -- 2.3.2 (Apple Git-55)