Skip to content

Commit

Permalink
require on Ractors
Browse files Browse the repository at this point in the history
Some of required libraries need to run on main Ractor because of
setting constants with unshareable objects and so on.

So this patch allows `require` by running `require` on the main
ractor by `rb_ractor_interrupt_exec()` (which makes a thread in
the specified racotr and run passed C-func on the created thread).

If it failed, the exception object will be (copy) sent from the
main ractor to the caller ractor and raises on it. If the transfer
failed (maybe because the exception object is not copy-able), tell
the failure of transfering.

Same on `require_relative` and `autoload`.
  • Loading branch information
ko1 committed Jul 10, 2024
1 parent d458441 commit 430855a
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 19 deletions.
82 changes: 71 additions & 11 deletions bootstraptest/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1822,3 +1811,74 @@ 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.rb')
return true if File.exist?(dummyfile)
File.write dummyfile, ''
begin
Ractor.new dummyfile do |f|
require_relative File.basename(f)
end.take
ensure
File.unlink dummyfile
end
}

# require_relative in Ractor
assert_equal 'true', %q{
dummyfile = File.join(__dir__, 'dummy.rb')
return true if File.exist?(dummyfile)
begin
Ractor.new dummyfile do |f|
require_relative File.basename(f)
false
end.take
rescue Ractor::RemoteError
true
end
}

# 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{
autoload :Benchmark, 'xyzzyxyzzy'
r = Ractor.new do
begin
Benchmark.measure{}
rescue LoadError => e
e.class
end
end
r.take
}
2 changes: 2 additions & 0 deletions ext/io/console/console.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
23 changes: 16 additions & 7 deletions load.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "darray.h"
#include "ruby/encoding.h"
#include "ruby/util.h"
#include "ractor_core.h"

static VALUE ruby_dln_libmap;

Expand Down Expand Up @@ -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
Expand Down
185 changes: 185 additions & 0 deletions ractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
27 changes: 27 additions & 0 deletions ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions ractor_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 rb_ractor_autoload_load(VALUE space, ID id);

VALUE rb_ractor_ensure_shareable(VALUE obj, VALUE name);

Expand Down
1 change: 1 addition & 0 deletions test/-ext-/symbol/test_type.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# frozen_string_literal: false

require 'test/unit'
require "-test-/symbol"

Expand Down
2 changes: 1 addition & 1 deletion variable.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 430855a

Please sign in to comment.