diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 0390d38f9c3408..d5120a22a6ef9b 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -211,17 +211,6 @@ Ractor.make_shareable(closure).call } -# Now autoload in non-main Ractor is not supported -assert_equal 'ok', %q{ - autoload :Foo, 'foo.rb' - r = Ractor.new do - p Foo - rescue Ractor::UnsafeError - :ok - end - r.take -} - ### ### # Ractor still has several memory corruption so skip huge number of tests @@ -1822,3 +1811,80 @@ class C8; def self.foo = 17; end shareable = Ractor.make_shareable("chilled") shareable == "chilled" && Ractor.shareable?(shareable) } + +# require in Ractor +assert_equal 'true', %q{ + Module.new do + def require feature + return Ractor.require(feature) unless Ractor.main? + super + end + Object.prepend self + set_temporary_name 'Ractor#require' + end + + Ractor.new{ + require 'benchmark' + Benchmark.measure{} + }.take.real > 0 +} + +# require_relative in Ractor +assert_equal 'true', %q{ + dummyfile = File.join(__dir__, "dummy#{rand}.rb") + return true if File.exist?(dummyfile) + + begin + File.write dummyfile, '' + rescue Exception + # skip on any errors + return true + end + + begin + Ractor.new dummyfile do |f| + require_relative File.basename(f) + end.take + ensure + File.unlink dummyfile + end +} + +# require_relative in Ractor +assert_equal 'LoadError', %q{ + dummyfile = File.join(__dir__, "not_existed_dummy#{rand}.rb") + return true if File.exist?(dummyfile) + + Ractor.new dummyfile do |f| + begin + require_relative File.basename(f) + rescue LoadError => e + e.class + end + end.take +} + +# autolaod in Ractor +assert_equal 'true', %q{ + autoload :Benchmark, 'benchmark' + + r = Ractor.new do + Benchmark.measure{} + end + r.take.real > 0 +} + +# failed in autolaod in Ractor +assert_equal 'LoadError', %q{ + dummyfile = File.join(__dir__, "not_existed_dummy#{rand}.rb") + autoload :Benchmark, dummyfile + + r = Ractor.new do + begin + Benchmark.measure{} + rescue LoadError => e + e.class + end + end + r.take +} diff --git a/ext/io/console/console.c b/ext/io/console/console.c index d88184ec9d1dea..d9a48a519455a6 100644 --- a/ext/io/console/console.c +++ b/ext/io/console/console.c @@ -1786,6 +1786,8 @@ Init_console(void) void InitVM_console(void) { + rb_ext_ractor_safe(true); + rb_define_method(rb_cIO, "raw", console_raw, -1); rb_define_method(rb_cIO, "raw!", console_set_raw, -1); rb_define_method(rb_cIO, "cooked", console_cooked, 0); diff --git a/load.c b/load.c index a1729c2ba10cbb..5c5e0592bc9097 100644 --- a/load.c +++ b/load.c @@ -18,6 +18,7 @@ #include "darray.h" #include "ruby/encoding.h" #include "ruby/util.h" +#include "ractor_core.h" static VALUE ruby_dln_libmap; @@ -1383,17 +1384,25 @@ static VALUE rb_require_string_internal(VALUE fname, bool resurrect) { rb_execution_context_t *ec = GET_EC(); - int result = require_internal(ec, fname, 1, RTEST(ruby_verbose)); - if (result > TAG_RETURN) { - EC_JUMP_TAG(ec, result); - } - if (result < 0) { + // main ractor check + if (!rb_ractor_main_p()) { if (resurrect) fname = rb_str_resurrect(fname); - load_failed(fname); + return rb_ractor_require(fname); } + else { + int result = require_internal(ec, fname, 1, RTEST(ruby_verbose)); + + if (result > TAG_RETURN) { + EC_JUMP_TAG(ec, result); + } + if (result < 0) { + if (resurrect) fname = rb_str_resurrect(fname); + load_failed(fname); + } - return RBOOL(result); + return RBOOL(result); + } } VALUE diff --git a/ractor.c b/ractor.c index 2323f6b9eb0039..e5d6931086f94b 100644 --- a/ractor.c +++ b/ractor.c @@ -3863,4 +3863,189 @@ ractor_local_value_set(rb_execution_context_t *ec, VALUE self, VALUE sym, VALUE return val; } +// Ractor::Channel (emulate with Ractor) + +typedef rb_ractor_t rb_ractor_channel_t; + +static VALUE +rb_ractor_channel_new(void) +{ + return rb_funcall(rb_const_get(rb_cRactor, rb_intern("Channel")), rb_intern("new"), 0); +} + +static VALUE +rb_ractor_channel_yield(VALUE vch, VALUE obj) +{ + rb_ractor_channel_t *ch = RACTOR_PTR(vch); + ractor_send(GET_EC(), (rb_ractor_t *)ch, obj, Qfalse); + return Qnil; +} + +static VALUE +rb_ractor_channel_take(VALUE vch) +{ + rb_ractor_channel_t *ch = RACTOR_PTR(vch); + return ractor_take(GET_EC(), (rb_ractor_t *)ch); +} + +static VALUE +rb_ractor_channel_close(VALUE vch) +{ + rb_ractor_channel_t *ch = RACTOR_PTR(vch); + return ractor_close_incoming(GET_EC(), (rb_ractor_t *)ch); +} + +// Ractor#require + +struct cross_ractor_require { + VALUE ch; + VALUE result; + VALUE exception; + + // require + VALUE feature; + + // autoload + VALUE module; + ID name; +}; + +static VALUE +require_body(VALUE data) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + + ID require; + CONST_ID(require, "require"); + crr->result = rb_funcallv(Qnil, require, 1, &crr->feature); + + return Qnil; +} + +static VALUE +require_rescue(VALUE data, VALUE errinfo) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + crr->exception = errinfo; + return Qundef; +} + +static VALUE +require_result_copy_body(VALUE data) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + + if (crr->exception != Qundef) { + VM_ASSERT(crr->result == Qnil); + crr->exception = ractor_copy(crr->exception); + } + else{ + VM_ASSERT(crr->result != Qundef); + crr->result = ractor_copy(crr->result); + } + + return Qnil; +} + +static VALUE +require_result_copy_resuce(VALUE data, VALUE errinfo) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + crr->exception = errinfo; // ractor_move(crr->exception); + return Qnil; +} + +static VALUE +ractor_require_protect(struct cross_ractor_require *crr, VALUE (*func)(VALUE)) +{ + // catch any error + rb_rescue2(func, (VALUE)crr, + require_rescue, (VALUE)crr, rb_eException, 0); + + rb_rescue2(require_result_copy_body, (VALUE)crr, + require_result_copy_resuce, (VALUE)crr, rb_eException, 0); + + rb_ractor_channel_yield(crr->ch, Qtrue); + return Qnil; + +} + +static VALUE +ractore_require_func(void *data) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + return ractor_require_protect(crr, require_body); +} + +VALUE +rb_ractor_require(VALUE feature) +{ + // TODO: make feature shareable + struct cross_ractor_require crr = { + .feature = feature, // TODO: ractor + .ch = rb_ractor_channel_new(), + .result = Qundef, + .exception = Qundef, + }; + + rb_ractor_t *r = GET_VM()->ractor.main_ractor; + rb_ractor_interrupt_exec(r, ractore_require_func, &crr, 0); + + // wait for require done + rb_ractor_channel_take(crr.ch); + + if (crr.exception != Qundef) { + rb_exc_raise(crr.exception); + } + else { + return crr.result; + } +} + +static VALUE +ractor_require(rb_execution_context_t *ec, VALUE self, VALUE feature) +{ + return rb_ractor_require(feature); +} + +static VALUE +autoload_load_body(VALUE data) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + crr->result = rb_autoload_load(crr->module, crr->name); + return Qnil; +} + +static VALUE +ractor_autoload_load_func(void *data) +{ + struct cross_ractor_require *crr = (struct cross_ractor_require *)data; + return ractor_require_protect(crr, autoload_load_body); +} + +VALUE +rb_ractor_autoload_load(VALUE module, ID name) +{ + struct cross_ractor_require crr = { + .module = module, + .name = name, + .ch = rb_ractor_channel_new(), + .result = Qundef, + .exception = Qundef, + }; + + rb_ractor_t *r = GET_VM()->ractor.main_ractor; + rb_ractor_interrupt_exec(r, ractor_autoload_load_func, &crr, 0); + + // wait for require done + rb_ractor_channel_take(crr.ch); + + if (crr.exception != Qundef) { + rb_exc_raise(crr.exception); + } + else { + return crr.result; + } +} + #include "ractor.rbinc" diff --git a/ractor.rb b/ractor.rb index d18062ca079919..b957b2b9999da4 100644 --- a/ractor.rb +++ b/ractor.rb @@ -844,10 +844,37 @@ def []=(sym, val) Primitive.ractor_local_value_set(sym, val) end + class Channel + def self.new + Ractor.new do + while true + obj = Ractor.receive + Ractor.yield obj + end + rescue Ractor::ClosedError + nil + end + end + end + # returns main ractor def self.main __builtin_cexpr! %q{ rb_ractor_self(GET_VM()->ractor.main_ractor); } end + + def self.main? + __builtin_cexpr! %q{ + GET_VM()->ractor.main_ractor == rb_ec_ractor_ptr(ec) + } + end + + def self.require feature + if main? + super feature + else + Primitive.ractor_require feature + end + end end diff --git a/ractor_core.h b/ractor_core.h index e35be2f21c10d8..315c3ecab610f5 100644 --- a/ractor_core.h +++ b/ractor_core.h @@ -228,6 +228,8 @@ void rb_ractor_terminate_interrupt_main_thread(rb_ractor_t *r); void rb_ractor_terminate_all(void); bool rb_ractor_main_p_(void); void rb_ractor_atfork(rb_vm_t *vm, rb_thread_t *th); +VALUE rb_ractor_require(VALUE feature); +VALUE rb_ractor_autoload_load(VALUE space, ID id); VALUE rb_ractor_ensure_shareable(VALUE obj, VALUE name); diff --git a/test/-ext-/symbol/test_type.rb b/test/-ext-/symbol/test_type.rb index 2b0fbe5b798b93..8029787bdd2385 100644 --- a/test/-ext-/symbol/test_type.rb +++ b/test/-ext-/symbol/test_type.rb @@ -1,4 +1,5 @@ # frozen_string_literal: false + require 'test/unit' require "-test-/symbol" diff --git a/variable.c b/variable.c index 8be3d9fdb5dfa2..fa065cad78e14d 100644 --- a/variable.c +++ b/variable.c @@ -2997,7 +2997,7 @@ rb_autoload_load(VALUE module, ID name) // At this point, we assume there might be autoloading, so fail if it's ractor: if (UNLIKELY(!rb_ractor_main_p())) { - rb_raise(rb_eRactorUnsafeError, "require by autoload on non-main Ractor is not supported (%s)", rb_id2name(name)); + return rb_ractor_autoload_load(module, name); } // This state is stored on the stack and is used during the autoload process.