зеркало из https://github.com/github/ruby.git
support `require` in non-main Ractors
Many libraries should be loaded on the main ractor because of setting constants with unshareable objects and so on. This patch allows to call `requore` on non-main Ractors by asking the main ractor to call `require` on it. The calling ractor waits for the result of `require` from the main ractor. If the `require` call failed with some reasons, an exception objects will be deliverred from the main ractor to the calling ractor if it is copy-able. Same on `require_relative` and `require` by `autoload`. Now `Ractor.new{pp obj}` works well (the first call of `pp` requires `pp` library implicitly). [Feature #20627]
This commit is contained in:
Родитель
075a102c93
Коммит
aa63699d10
|
@ -211,17 +211,6 @@ assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{
|
|||
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
|
||||
|
@ -1836,3 +1825,80 @@ assert_equal 'true', %q{
|
|||
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
|
||||
}
|
||||
|
|
23
load.c
23
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));
|
||||
|
||||
return RBOOL(result);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
VALUE
|
||||
|
|
248
ractor.c
248
ractor.c
|
@ -1956,6 +1956,7 @@ cancel_single_ractor_mode(void)
|
|||
}
|
||||
|
||||
ruby_single_main_ractor = NULL;
|
||||
rb_funcall(rb_cRactor, rb_intern("_activated"), 0);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -2136,6 +2137,13 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL
|
|||
return rv;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ractor_create_func(VALUE klass, VALUE loc, VALUE name, VALUE args, rb_block_call_func_t func)
|
||||
{
|
||||
VALUE block = rb_proc_new(func, Qnil);
|
||||
return ractor_create(rb_current_ec_noinline(), klass, loc, name, args, block);
|
||||
}
|
||||
|
||||
static void
|
||||
ractor_yield_atexit(rb_execution_context_t *ec, rb_ractor_t *cr, VALUE v, bool exc)
|
||||
{
|
||||
|
@ -2665,6 +2673,8 @@ Init_Ractor(void)
|
|||
rb_define_method(rb_cRactorMovedObject, "instance_eval", ractor_moved_missing, -1);
|
||||
rb_define_method(rb_cRactorMovedObject, "instance_exec", ractor_moved_missing, -1);
|
||||
|
||||
// internal
|
||||
|
||||
#if USE_RACTOR_SELECTOR
|
||||
rb_init_ractor_selector();
|
||||
#endif
|
||||
|
@ -3869,4 +3879,242 @@ 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
|
||||
ractor_channel_func(RB_BLOCK_CALL_FUNC_ARGLIST(y, c))
|
||||
{
|
||||
rb_execution_context_t *ec = GET_EC();
|
||||
rb_ractor_t *cr = rb_ec_ractor_ptr(ec);
|
||||
|
||||
while (1) {
|
||||
int state;
|
||||
|
||||
EC_PUSH_TAG(ec);
|
||||
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
|
||||
VALUE obj = ractor_receive(ec, cr);
|
||||
ractor_yield(ec, cr, obj, Qfalse);
|
||||
}
|
||||
EC_POP_TAG();
|
||||
|
||||
if (state) {
|
||||
// ignore the error
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_ractor_channel_new(void)
|
||||
{
|
||||
#if 0
|
||||
return rb_funcall(rb_const_get(rb_cRactor, rb_intern("Channel")), rb_intern("new"), 0);
|
||||
#else
|
||||
// class Channel
|
||||
// def self.new
|
||||
// Ractor.new do # func body
|
||||
// while true
|
||||
// obj = Ractor.receive
|
||||
// Ractor.yield obj
|
||||
// end
|
||||
// rescue Ractor::ClosedError
|
||||
// nil
|
||||
// end
|
||||
// end
|
||||
// end
|
||||
|
||||
return ractor_create_func(rb_cRactor, Qnil, rb_str_new2("Ractor/channel"), rb_ary_new(), ractor_channel_func);
|
||||
#endif
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_ractor_channel_yield(rb_execution_context_t *ec, VALUE vch, VALUE obj)
|
||||
{
|
||||
VM_ASSERT(ec == rb_current_ec_noinline());
|
||||
rb_ractor_channel_t *ch = RACTOR_PTR(vch);
|
||||
|
||||
ractor_send(ec, (rb_ractor_t *)ch, obj, Qfalse);
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_ractor_channel_take(rb_execution_context_t *ec, VALUE vch)
|
||||
{
|
||||
VM_ASSERT(ec == rb_current_ec_noinline());
|
||||
rb_ractor_channel_t *ch = RACTOR_PTR(vch);
|
||||
|
||||
return ractor_take(ec, (rb_ractor_t *)ch);
|
||||
}
|
||||
|
||||
static VALUE
|
||||
rb_ractor_channel_close(rb_execution_context_t *ec, VALUE vch)
|
||||
{
|
||||
VM_ASSERT(ec == rb_current_ec_noinline());
|
||||
rb_ractor_channel_t *ch = RACTOR_PTR(vch);
|
||||
|
||||
ractor_close_incoming(ec, (rb_ractor_t *)ch);
|
||||
return ractor_close_outgoing(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 == Qundef);
|
||||
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(GET_EC(), 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_execution_context_t *ec = GET_EC();
|
||||
rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
|
||||
rb_ractor_interrupt_exec(main_r, ractore_require_func, &crr, 0);
|
||||
|
||||
// wait for require done
|
||||
rb_ractor_channel_take(ec, crr.ch);
|
||||
rb_ractor_channel_close(ec, 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_execution_context_t *ec = GET_EC();
|
||||
rb_ractor_t *main_r = GET_VM()->ractor.main_ractor;
|
||||
rb_ractor_interrupt_exec(main_r, ractor_autoload_load_func, &crr, 0);
|
||||
|
||||
// wait for require done
|
||||
rb_ractor_channel_take(ec, crr.ch);
|
||||
rb_ractor_channel_close(ec, crr.ch);
|
||||
|
||||
if (crr.exception != Qundef) {
|
||||
rb_exc_raise(crr.exception);
|
||||
}
|
||||
else {
|
||||
return crr.result;
|
||||
}
|
||||
}
|
||||
|
||||
#include "ractor.rbinc"
|
||||
|
|
30
ractor.rb
30
ractor.rb
|
@ -835,11 +835,13 @@ class Ractor
|
|||
end
|
||||
|
||||
# get a value from ractor-local storage of current Ractor
|
||||
# Obsolete and use Ractor.[] instead.
|
||||
def [](sym)
|
||||
Primitive.ractor_local_value(sym)
|
||||
end
|
||||
|
||||
# set a value in ractor-local storage of current Ractor
|
||||
# Obsolete and use Ractor.[]= instead.
|
||||
def []=(sym, val)
|
||||
Primitive.ractor_local_value_set(sym, val)
|
||||
end
|
||||
|
@ -867,4 +869,32 @@ class Ractor
|
|||
GET_VM()->ractor.main_ractor == rb_ec_ractor_ptr(ec)
|
||||
}
|
||||
end
|
||||
|
||||
# internal method
|
||||
def self._require feature
|
||||
if main?
|
||||
super feature
|
||||
else
|
||||
Primitive.ractor_require feature
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
private
|
||||
|
||||
# internal method that is called when the first "Ractor.new" is called
|
||||
def _activated
|
||||
Kernel.prepend Module.new{|m|
|
||||
m.set_temporary_name '<RactorRequire>'
|
||||
|
||||
def require feature
|
||||
if Ractor.main?
|
||||
super
|
||||
else
|
||||
Ractor._require feature
|
||||
end
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -222,6 +222,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);
|
||||
|
||||
|
|
|
@ -845,7 +845,7 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b
|
|||
RUBY_DEBUG_LOG("th:%u", rb_th_serial(th));
|
||||
|
||||
ASSERT_thread_sched_locked(sched, th);
|
||||
VM_ASSERT(th == GET_THREAD());
|
||||
VM_ASSERT(th == rb_ec_thread_ptr(rb_current_ec_noinline()));
|
||||
|
||||
if (th != sched->running) {
|
||||
// already deleted from running threads
|
||||
|
@ -900,12 +900,12 @@ thread_sched_wait_running_turn(struct rb_thread_sched *sched, rb_thread_t *th, b
|
|||
thread_sched_set_lock_owner(sched, th);
|
||||
}
|
||||
|
||||
VM_ASSERT(GET_EC() == th->ec);
|
||||
VM_ASSERT(rb_current_ec_noinline() == th->ec);
|
||||
}
|
||||
}
|
||||
|
||||
VM_ASSERT(th->nt != NULL);
|
||||
VM_ASSERT(GET_EC() == th->ec);
|
||||
VM_ASSERT(rb_current_ec_noinline() == th->ec);
|
||||
VM_ASSERT(th->sched.waiting_reason.flags == thread_sched_waiting_none);
|
||||
|
||||
// add th to running threads
|
||||
|
|
|
@ -132,7 +132,6 @@ struct rb_thread_sched {
|
|||
|
||||
#ifdef RB_THREAD_LOCAL_SPECIFIER
|
||||
NOINLINE(void rb_current_ec_set(struct rb_execution_context_struct *));
|
||||
NOINLINE(struct rb_execution_context_struct *rb_current_ec_noinline(void));
|
||||
|
||||
# ifdef __APPLE__
|
||||
// on Darwin, TLS can not be accessed across .so
|
||||
|
|
|
@ -2994,7 +2994,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.
|
||||
|
|
10
vm.c
10
vm.c
|
@ -556,7 +556,7 @@ RB_THREAD_LOCAL_SPECIFIER rb_execution_context_t *ruby_current_ec;
|
|||
RB_THREAD_LOCAL_SPECIFIER rb_atomic_t ruby_nt_serial;
|
||||
#endif
|
||||
|
||||
// no-inline decl on thread_pthread.h
|
||||
// no-inline decl on vm_core.h
|
||||
rb_execution_context_t *
|
||||
rb_current_ec_noinline(void)
|
||||
{
|
||||
|
@ -580,6 +580,14 @@ rb_current_ec(void)
|
|||
#endif
|
||||
#else
|
||||
native_tls_key_t ruby_current_ec_key;
|
||||
|
||||
// no-inline decl on vm_core.h
|
||||
rb_execution_context_t *
|
||||
rb_current_ec_noinline(void)
|
||||
{
|
||||
return native_tls_get(ruby_current_ec_key);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
rb_event_flag_t ruby_vm_event_flags;
|
||||
|
|
|
@ -1969,6 +1969,8 @@ rb_ec_vm_ptr(const rb_execution_context_t *ec)
|
|||
}
|
||||
}
|
||||
|
||||
NOINLINE(struct rb_execution_context_struct *rb_current_ec_noinline(void));
|
||||
|
||||
static inline rb_execution_context_t *
|
||||
rb_current_execution_context(bool expect_ec)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче