Skip to content

Ractor shareable proc #13926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,13 @@ Note: We're only listing outstanding class updates.

* `Ractor#close_incoming` and `Ractor#close_outgoing` were removed.

* `Ractor.sharealbe_proc` and `Ractor.shareable_lambda` is introduced
to make shareable Proc or lambda.
[[Feature #21039]]

* `Ractor.make_shaerable(a_proc)` is no longer supported.
[[Feature #21039]]

* Set

* Set is now a core class, instead of an autoloaded stdlib class.
Expand Down Expand Up @@ -195,6 +202,9 @@ The following bundled gems are updated.

[[Feature #21262]]

* `Ractor.make_sharealbe(a_proc)` is no longer supported. Use `Ractor.shareable_proc` instead.
[[[Feature #21039]]

## Stdlib compatibility issues

* CGI library is removed from the default gems. Now we only provide `cgi/escape` for
Expand Down Expand Up @@ -243,3 +253,4 @@ The following bundled gems are updated.
[Feature #21262]: https://bugs.ruby-lang.org/issues/21262
[Feature #21287]: https://bugs.ruby-lang.org/issues/21287
[Feature #21347]: https://bugs.ruby-lang.org/issues/21347
[Feature #21039]: https://bugs.ruby-lang.org/issues/21039
110 changes: 24 additions & 86 deletions bootstraptest/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -148,25 +148,25 @@
# Ractor.make_shareable issue for locals in proc [Bug #18023]
assert_equal '[:a, :b, :c, :d, :e]', %q{
v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e
closure = Ractor.current.instance_eval{ Proc.new { [v1, v2, v3, v4, v5] } }
closure = Ractor.shareable_lambda { [v1, v2, v3, v4, v5] }

Ractor.make_shareable(closure).call
closure.call
}

# Ractor.make_shareable issue for locals in proc [Bug #18023]
assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{
a = :a
closure = Ractor.current.instance_eval do
-> {
closure = Ractor.shareable_lambda do
Ractor.shareable_lambda {
b, c, d = :b, :c, :d
-> {
Ractor.shareable_lambda {
e, f, g = :e, :f, :g
-> { [a, b, c, d, e, f, g] }
Ractor.shareable_lambda { [a, b, c, d, e, f, g] }
}.call
}.call
end
end.call

Ractor.make_shareable(closure).call
closure.call
}

###
Expand Down Expand Up @@ -967,7 +967,7 @@ class C
end
RUBY

# Constant cache should care about non-sharable constants
# Constant cache should care about non-shareable constants
assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false
STR = "hello"
def str; STR; end
Expand Down Expand Up @@ -1137,41 +1137,17 @@ def /(other)
[a.frozen?, a[0].frozen?] == [true, false]
}

# Ractor.make_shareable(a_proc) makes a proc shareable.
# Ractor.make_shareable(a_proc) is not supported now.
assert_equal 'true', %q{
a = [1, [2, 3], {a: "4"}]

pr = Ractor.current.instance_eval do
Proc.new do
a
end
end

Ractor.make_shareable(a) # referred value should be shareable
Ractor.make_shareable(pr)
Ractor.shareable?(pr)
}

# Ractor.make_shareable(a_proc) makes inner structure shareable and freezes it
assert_equal 'true,true,true,true', %q{
class Proc
attr_reader :obj
def initialize
@obj = Object.new
end
end
pr = Proc.new{}

pr = Ractor.current.instance_eval do
Proc.new {}
begin
Ractor.make_shareable(pr)
rescue Ractor::Error
true
else
false
end

results = []
Ractor.make_shareable(pr)
results << Ractor.shareable?(pr)
results << pr.frozen?
results << Ractor.shareable?(pr.obj)
results << pr.obj.frozen?
results.map(&:to_s).join(',')
}

# Ractor.shareable?(recursive_objects)
Expand Down Expand Up @@ -1203,7 +1179,7 @@ module M; end
}

# Ractor.make_shareable with curried proc checks isolation of original proc
assert_equal 'isolation error', %q{
assert_equal 'true', %q{
a = Object.new
orig = proc { a }
curried = orig.curry
Expand All @@ -1212,6 +1188,8 @@ module M; end
Ractor.make_shareable(curried)
rescue Ractor::IsolationError
'isolation error'
rescue Ractor::Error => e
e.message.match?(/can not make shareable object/)
else
'no error'
end
Expand All @@ -1221,7 +1199,7 @@ module M; end
assert_equal '1', %q{
class C
a = 1
define_method "foo", Ractor.make_shareable(Proc.new{ a })
define_method "foo", Ractor.shareable_proc{a}
a = 2
end

Expand All @@ -1230,17 +1208,13 @@ class C

# Ractor.make_shareable(a_proc) makes a proc shareable.
assert_equal 'can not make a Proc shareable because it accesses outer variables (a).', %q{
a = b = nil
pr = Ractor.current.instance_eval do
Proc.new do
begin
a = b = nil
pr = Ractor.shareable_proc do
c = b # assign to a is okay because c is block local variable
# reading b is okay
a = b # assign to a is not allowed #=> Ractor::Error
end
end

begin
Ractor.make_shareable(pr)
rescue => e
e.message
end
Expand Down Expand Up @@ -1463,42 +1437,6 @@ class C
"ok"
} if !yjit_enabled? && ENV['GITHUB_WORKFLOW'] != 'ModGC' # flaky

assert_equal "ok", %q{
def foo(*); ->{ super }; end
begin
Ractor.make_shareable(foo)
rescue Ractor::IsolationError
"ok"
end
}

assert_equal "ok", %q{
def foo(**); ->{ super }; end
begin
Ractor.make_shareable(foo)
rescue Ractor::IsolationError
"ok"
end
}

assert_equal "ok", %q{
def foo(...); ->{ super }; end
begin
Ractor.make_shareable(foo)
rescue Ractor::IsolationError
"ok"
end
}

assert_equal "ok", %q{
def foo((x), (y)); ->{ super }; end
begin
Ractor.make_shareable(foo([], []))
rescue Ractor::IsolationError
"ok"
end
}

# check method cache invalidation
assert_equal "ok", %q{
module M
Expand Down
30 changes: 20 additions & 10 deletions proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -868,35 +868,45 @@ rb_block_lambda(void)
return proc_new(rb_cProc, TRUE);
}

static void
f_lambda_filter_non_literal(void)
bool
rb_literal_block_given_p(rb_control_frame_t *cfp, bool allow_lambda)
{
rb_control_frame_t *cfp = GET_EC()->cfp;
VALUE block_handler = rb_vm_frame_block_handler(cfp);

if (block_handler == VM_BLOCK_HANDLER_NONE) {
// no block error raised else where
return;
return false;
}

switch (vm_block_handler_type(block_handler)) {
case block_handler_type_iseq:
if (RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)->ep == VM_BH_TO_ISEQ_BLOCK(block_handler)->ep) {
return;
return true;
}
break;
case block_handler_type_symbol:
return;
return true;
case block_handler_type_proc:
if (rb_proc_lambda_p(VM_BH_TO_PROC(block_handler))) {
return;
if (allow_lambda && rb_proc_lambda_p(VM_BH_TO_PROC(block_handler))) {
return true;
}
break;
case block_handler_type_ifunc:
break;
}

rb_raise(rb_eArgError, "the lambda method requires a literal block");
return false;
}

static void
f_lambda_filter_non_literal(void)
{
rb_control_frame_t *cfp = GET_EC()->cfp;
if (!rb_block_given_p() || rb_literal_block_given_p(cfp, true)) {
return;
}
else {
rb_raise(rb_eArgError, "the lambda method requires a literal block");
}
}

/*
Expand Down
25 changes: 18 additions & 7 deletions ractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -1349,13 +1349,7 @@ make_shareable_check_shareable(VALUE obj)
return traverse_skip;
}
else if (!allow_frozen_shareable_p(obj)) {
if (rb_obj_is_proc(obj)) {
rb_proc_ractor_make_shareable(obj);
return traverse_cont;
}
else {
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
}
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
}

switch (TYPE(obj)) {
Expand Down Expand Up @@ -2250,6 +2244,23 @@ ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE
return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data);
}

// sharable_proc

static VALUE
ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_lambda)
{
if (!rb_literal_block_given_p(ec->cfp, false)) {
rb_raise(rb_eArgError, "requires a literal block");
}
else if (!rb_ractor_shareable_p(replace_self)) {
rb_raise(rb_eRactorIsolationError, "self should be shareable: %" PRIsVALUE, replace_self);
}
else {
VALUE proc = is_lambda ? rb_block_lambda() : rb_block_proc();
return rb_proc_ractor_make_shareable(proc, replace_self);
}
}

// Ractor#require

struct cross_ractor_require {
Expand Down
41 changes: 39 additions & 2 deletions ractor.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# \Ractor is an Actor-model abstraction for Ruby that provides thread-safe parallel execution.
#
# Ractor.new makes a new \Ractor, which can run in parallel.
#
# # The simplest ractor
Expand Down Expand Up @@ -612,6 +610,45 @@ def unmonitor port
__builtin_ractor_unmonitor(port)
end

#
# call-seq:
# Ractor.sharable_proc(self: nil){} -> sharable proc
#
# It returns shareable Proc object. The Proc object is
# shareable and the self in a block will be replaced with
# the value passed via `self:` keyword.
#
# In a shareable Proc, the outer variables are read-only
# and the outer variables should point only shareable objects.
#
# a = 42
# b = []
# Ractor.shareable_proc{ p a }.call # ok
# Ractor.shareable_proc{ p b }
# #=> can not make shareable Proc because it can refer unshareable object [] from variable 'b' (Ractor::IsolationError)
#
def self.shareable_proc self: nil
Primitive.attr! :use_block

__builtin_cexpr!(%Q{
ractor_shareable_proc(ec, self_keyword_parameter, false)
})
end

#
# call-seq:
# Ractor.sharable_proc{} -> sharable proc
#
# Same as Ractor.sharable_proc, but returns lambda proc.
#
def self.shareable_lambda self: nil
Primitive.attr! :use_block

__builtin_cexpr!(%Q{
ractor_shareable_proc(ec, self_keyword_parameter, true)
})
end

class Port
#
# call-seq:
Expand Down
19 changes: 7 additions & 12 deletions test/ruby/test_iseq.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,7 @@ def (Object.new).touch(**) # :nodoc:
def test_lambda_with_ractor_roundtrip
iseq = compile(<<~EOF, __LINE__+1)
x = 42
y = nil.instance_eval{ lambda { x } }
Ractor.make_shareable(y)
y = Ractor.shareable_lambda{x}
y.call
EOF
assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
Expand All @@ -158,22 +157,18 @@ def (Object.new).touch(&) # :nodoc:

def test_ractor_unshareable_outer_variable
name = "\u{2603 26a1}"
y = nil.instance_eval do
eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
end
assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
Ractor.make_shareable(y)
end
y = nil.instance_eval do
eval("proc {#{name} = []; proc {|x| #{name}}}").call
eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
end

assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do
Ractor.make_shareable(y)
eval("#{name} = []; Ractor.shareable_proc{#{name}}")
end

obj = Object.new
def obj.foo(*) nil.instance_eval{ ->{super} } end
def obj.foo(*) Ractor.shareable_proc{super} end
assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do
Ractor.make_shareable(obj.foo(*[]))
obj.foo(*[])
end
end

Expand Down
Loading
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy