Skip to content

Commit 43a14f6

Browse files
committed
Ractor.shareable_proc
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) ``` Ractor.sharaeble_lambda is also introduced. Ractor.make_sharable(a_proc) is no longer supported. [Feature #21039]
1 parent 4542ec4 commit 43a14f6

File tree

9 files changed

+129
-143
lines changed

9 files changed

+129
-143
lines changed

NEWS.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ Note: We're only listing outstanding class updates.
9494

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

97+
* `Ractor.sharealbe_proc` and `Ractor.shareable_lambda` is introduced
98+
to make shareable Proc or lambda.
99+
[[Feature #21039]]
100+
101+
* `Ractor.make_shaerable(a_proc)` is no longer supported.
102+
[[Feature #21039]]
103+
97104
* Set
98105

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

196203
[[Feature #21262]]
197204

205+
* `Ractor.make_sharealbe(a_proc)` is no longer supported. Use `Ractor.shareable_proc` instead.
206+
[[[Feature #21039]]
207+
198208
## Stdlib compatibility issues
199209

200210
* CGI library is removed from the default gems. Now we only provide `cgi/escape` for
@@ -243,3 +253,4 @@ The following bundled gems are updated.
243253
[Feature #21262]: https://bugs.ruby-lang.org/issues/21262
244254
[Feature #21287]: https://bugs.ruby-lang.org/issues/21287
245255
[Feature #21347]: https://bugs.ruby-lang.org/issues/21347
256+
[Feature #21039]: https://bugs.ruby-lang.org/issues/21039

bootstraptest/test_ractor.rb

Lines changed: 24 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -148,25 +148,25 @@
148148
# Ractor.make_shareable issue for locals in proc [Bug #18023]
149149
assert_equal '[:a, :b, :c, :d, :e]', %q{
150150
v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e
151-
closure = Ractor.current.instance_eval{ Proc.new { [v1, v2, v3, v4, v5] } }
151+
closure = Ractor.shareable_lambda { [v1, v2, v3, v4, v5] }
152152
153-
Ractor.make_shareable(closure).call
153+
closure.call
154154
}
155155

156156
# Ractor.make_shareable issue for locals in proc [Bug #18023]
157157
assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{
158158
a = :a
159-
closure = Ractor.current.instance_eval do
160-
-> {
159+
closure = Ractor.shareable_lambda do
160+
Ractor.shareable_lambda {
161161
b, c, d = :b, :c, :d
162-
-> {
162+
Ractor.shareable_lambda {
163163
e, f, g = :e, :f, :g
164-
-> { [a, b, c, d, e, f, g] }
164+
Ractor.shareable_lambda { [a, b, c, d, e, f, g] }
165165
}.call
166166
}.call
167-
end
167+
end.call
168168
169-
Ractor.make_shareable(closure).call
169+
closure.call
170170
}
171171

172172
###
@@ -967,7 +967,7 @@ class C
967967
end
968968
RUBY
969969

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

1140-
# Ractor.make_shareable(a_proc) makes a proc shareable.
1140+
# Ractor.make_shareable(a_proc) is not supported now.
11411141
assert_equal 'true', %q{
1142-
a = [1, [2, 3], {a: "4"}]
1143-
1144-
pr = Ractor.current.instance_eval do
1145-
Proc.new do
1146-
a
1147-
end
1148-
end
1149-
1150-
Ractor.make_shareable(a) # referred value should be shareable
1151-
Ractor.make_shareable(pr)
1152-
Ractor.shareable?(pr)
1153-
}
1154-
1155-
# Ractor.make_shareable(a_proc) makes inner structure shareable and freezes it
1156-
assert_equal 'true,true,true,true', %q{
1157-
class Proc
1158-
attr_reader :obj
1159-
def initialize
1160-
@obj = Object.new
1161-
end
1162-
end
1142+
pr = Proc.new{}
11631143
1164-
pr = Ractor.current.instance_eval do
1165-
Proc.new {}
1144+
begin
1145+
Ractor.make_shareable(pr)
1146+
rescue Ractor::Error
1147+
true
1148+
else
1149+
false
11661150
end
1167-
1168-
results = []
1169-
Ractor.make_shareable(pr)
1170-
results << Ractor.shareable?(pr)
1171-
results << pr.frozen?
1172-
results << Ractor.shareable?(pr.obj)
1173-
results << pr.obj.frozen?
1174-
results.map(&:to_s).join(',')
11751151
}
11761152

11771153
# Ractor.shareable?(recursive_objects)
@@ -1203,7 +1179,7 @@ module M; end
12031179
}
12041180

12051181
# Ractor.make_shareable with curried proc checks isolation of original proc
1206-
assert_equal 'isolation error', %q{
1182+
assert_equal 'true', %q{
12071183
a = Object.new
12081184
orig = proc { a }
12091185
curried = orig.curry
@@ -1212,6 +1188,8 @@ module M; end
12121188
Ractor.make_shareable(curried)
12131189
rescue Ractor::IsolationError
12141190
'isolation error'
1191+
rescue Ractor::Error => e
1192+
e.message.match?(/can not make shareable object/)
12151193
else
12161194
'no error'
12171195
end
@@ -1221,7 +1199,7 @@ module M; end
12211199
assert_equal '1', %q{
12221200
class C
12231201
a = 1
1224-
define_method "foo", Ractor.make_shareable(Proc.new{ a })
1202+
define_method "foo", Ractor.shareable_proc{a}
12251203
a = 2
12261204
end
12271205
@@ -1230,17 +1208,13 @@ class C
12301208

12311209
# Ractor.make_shareable(a_proc) makes a proc shareable.
12321210
assert_equal 'can not make a Proc shareable because it accesses outer variables (a).', %q{
1233-
a = b = nil
1234-
pr = Ractor.current.instance_eval do
1235-
Proc.new do
1211+
begin
1212+
a = b = nil
1213+
pr = Ractor.shareable_proc do
12361214
c = b # assign to a is okay because c is block local variable
12371215
# reading b is okay
12381216
a = b # assign to a is not allowed #=> Ractor::Error
12391217
end
1240-
end
1241-
1242-
begin
1243-
Ractor.make_shareable(pr)
12441218
rescue => e
12451219
e.message
12461220
end
@@ -1463,42 +1437,6 @@ class C
14631437
"ok"
14641438
} if !yjit_enabled? && ENV['GITHUB_WORKFLOW'] != 'ModGC' # flaky
14651439

1466-
assert_equal "ok", %q{
1467-
def foo(*); ->{ super }; end
1468-
begin
1469-
Ractor.make_shareable(foo)
1470-
rescue Ractor::IsolationError
1471-
"ok"
1472-
end
1473-
}
1474-
1475-
assert_equal "ok", %q{
1476-
def foo(**); ->{ super }; end
1477-
begin
1478-
Ractor.make_shareable(foo)
1479-
rescue Ractor::IsolationError
1480-
"ok"
1481-
end
1482-
}
1483-
1484-
assert_equal "ok", %q{
1485-
def foo(...); ->{ super }; end
1486-
begin
1487-
Ractor.make_shareable(foo)
1488-
rescue Ractor::IsolationError
1489-
"ok"
1490-
end
1491-
}
1492-
1493-
assert_equal "ok", %q{
1494-
def foo((x), (y)); ->{ super }; end
1495-
begin
1496-
Ractor.make_shareable(foo([], []))
1497-
rescue Ractor::IsolationError
1498-
"ok"
1499-
end
1500-
}
1501-
15021440
# check method cache invalidation
15031441
assert_equal "ok", %q{
15041442
module M

proc.c

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -868,35 +868,45 @@ rb_block_lambda(void)
868868
return proc_new(rb_cProc, TRUE);
869869
}
870870

871-
static void
872-
f_lambda_filter_non_literal(void)
871+
bool
872+
rb_literal_block_given_p(rb_control_frame_t *cfp, bool allow_lambda)
873873
{
874-
rb_control_frame_t *cfp = GET_EC()->cfp;
875874
VALUE block_handler = rb_vm_frame_block_handler(cfp);
876875

877876
if (block_handler == VM_BLOCK_HANDLER_NONE) {
878-
// no block error raised else where
879-
return;
877+
return false;
880878
}
881879

882880
switch (vm_block_handler_type(block_handler)) {
883881
case block_handler_type_iseq:
884882
if (RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp)->ep == VM_BH_TO_ISEQ_BLOCK(block_handler)->ep) {
885-
return;
883+
return true;
886884
}
887885
break;
888886
case block_handler_type_symbol:
889-
return;
887+
return true;
890888
case block_handler_type_proc:
891-
if (rb_proc_lambda_p(VM_BH_TO_PROC(block_handler))) {
892-
return;
889+
if (allow_lambda && rb_proc_lambda_p(VM_BH_TO_PROC(block_handler))) {
890+
return true;
893891
}
894892
break;
895893
case block_handler_type_ifunc:
896894
break;
897895
}
898896

899-
rb_raise(rb_eArgError, "the lambda method requires a literal block");
897+
return false;
898+
}
899+
900+
static void
901+
f_lambda_filter_non_literal(void)
902+
{
903+
rb_control_frame_t *cfp = GET_EC()->cfp;
904+
if (!rb_block_given_p() || rb_literal_block_given_p(cfp, true)) {
905+
return;
906+
}
907+
else {
908+
rb_raise(rb_eArgError, "the lambda method requires a literal block");
909+
}
900910
}
901911

902912
/*

ractor.c

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,13 +1349,7 @@ make_shareable_check_shareable(VALUE obj)
13491349
return traverse_skip;
13501350
}
13511351
else if (!allow_frozen_shareable_p(obj)) {
1352-
if (rb_obj_is_proc(obj)) {
1353-
rb_proc_ractor_make_shareable(obj);
1354-
return traverse_cont;
1355-
}
1356-
else {
1357-
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
1358-
}
1352+
rb_raise(rb_eRactorError, "can not make shareable object for %+"PRIsVALUE, obj);
13591353
}
13601354

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

2247+
// sharable_proc
2248+
2249+
static VALUE
2250+
ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_lambda)
2251+
{
2252+
if (!rb_literal_block_given_p(ec->cfp, false)) {
2253+
rb_raise(rb_eArgError, "requires a literal block");
2254+
}
2255+
else if (!rb_ractor_shareable_p(replace_self)) {
2256+
rb_raise(rb_eRactorIsolationError, "self should be shareable: %" PRIsVALUE, replace_self);
2257+
}
2258+
else {
2259+
VALUE proc = is_lambda ? rb_block_lambda() : rb_block_proc();
2260+
return rb_proc_ractor_make_shareable(proc, replace_self);
2261+
}
2262+
}
2263+
22532264
// Ractor#require
22542265

22552266
struct cross_ractor_require {

ractor.rb

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
# \Ractor is an Actor-model abstraction for Ruby that provides thread-safe parallel execution.
2-
#
31
# Ractor.new makes a new \Ractor, which can run in parallel.
42
#
53
# # The simplest ractor
@@ -612,6 +610,41 @@ def unmonitor port
612610
__builtin_ractor_unmonitor(port)
613611
end
614612

613+
#
614+
# call-seq:
615+
# Ractor.sharable_proc(self: nil){} -> sharable proc
616+
#
617+
# It returns shareable Proc object. The Proc object is
618+
# shareable and the self in a block will be replaced with
619+
# the value passed via `self:` keyword.
620+
#
621+
# In a shareable Proc, the outer variables are read-only
622+
# and the outer variables should point only shareable objects.
623+
#
624+
# a = 42
625+
# b = []
626+
# Ractor.shareable_proc{ p a }.call # ok
627+
# Ractor.shareable_proc{ p b }
628+
# #=> can not make shareable Proc because it can refer unshareable object [] from variable 'b' (Ractor::IsolationError)
629+
#
630+
def self.shareable_proc self: nil
631+
__builtin_cexpr!(%Q{
632+
ractor_shareable_proc(ec, self_keyword_parameter, false)
633+
})
634+
end
635+
636+
#
637+
# call-seq:
638+
# Ractor.sharable_proc{} -> sharable proc
639+
#
640+
# Same as Ractor.sharable_proc, but returns lambda proc.
641+
#
642+
def self.shareable_lambda self: nil
643+
__builtin_cexpr!(%Q{
644+
ractor_shareable_proc(ec, self_keyword_parameter, true)
645+
})
646+
end
647+
615648
class Port
616649
#
617650
# call-seq:

test/ruby/test_iseq.rb

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ def (Object.new).touch(**) # :nodoc:
139139
def test_lambda_with_ractor_roundtrip
140140
iseq = compile(<<~EOF, __LINE__+1)
141141
x = 42
142-
y = nil.instance_eval{ lambda { x } }
143-
Ractor.make_shareable(y)
142+
y = Ractor.shareable_lambda{x}
144143
y.call
145144
EOF
146145
assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval)
@@ -158,22 +157,18 @@ def (Object.new).touch(&) # :nodoc:
158157

159158
def test_ractor_unshareable_outer_variable
160159
name = "\u{2603 26a1}"
161-
y = nil.instance_eval do
162-
eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call
163-
end
164160
assert_raise_with_message(ArgumentError, /\(#{name}\)/) do
165-
Ractor.make_shareable(y)
166-
end
167-
y = nil.instance_eval do
168-
eval("proc {#{name} = []; proc {|x| #{name}}}").call
161+
eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}")
169162
end
163+
170164
assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do
171-
Ractor.make_shareable(y)
165+
eval("#{name} = []; Ractor.shareable_proc{#{name}}")
172166
end
167+
173168
obj = Object.new
174-
def obj.foo(*) nil.instance_eval{ ->{super} } end
169+
def obj.foo(*) Ractor.shareable_proc{super} end
175170
assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do
176-
Ractor.make_shareable(obj.foo(*[]))
171+
obj.foo(*[])
177172
end
178173
end
179174

0 commit comments

Comments
 (0)
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