Skip to content
Merged
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
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ Note: We're only listing outstanding class updates.

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

* `Ractor.shareable_proc` and `Ractor.shareable_lambda` is introduced
to make shareable Proc or lambda.
[[Feature #21550]], [[Feature #21557]]

* `Set`

* `Set` is now a core class, instead of an autoloaded stdlib class.
Expand Down Expand Up @@ -316,3 +320,5 @@ A lot of work has gone into making Ractors more stable, performant, and usable.
[Feature #21347]: https://bugs.ruby-lang.org/issues/21347
[Feature #21360]: https://bugs.ruby-lang.org/issues/21360
[Feature #21527]: https://bugs.ruby-lang.org/issues/21527
[Feature #21550]: https://bugs.ruby-lang.org/issues/21550
[Feature #21557]: https://bugs.ruby-lang.org/issues/21557
163 changes: 44 additions & 119 deletions bootstraptest/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,28 +145,47 @@
}.map(&:value)
}

assert_equal "42", %q{
a = 42
Ractor.shareable_lambda{ a }.call
}

# 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] } }

Ractor.make_shareable(closure).call
closure = Proc.new { [v1, v2, v3, v4, v5] }
Ractor.shareable_proc(&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
-> {
b, c, d = :b, :c, :d
-> {
e, f, g = :e, :f, :g
-> { [a, b, c, d, e, f, g] }
}.call
}.call
# Ractor::IsolationError cases
assert_equal '3', %q{
ok = 0

begin
a = 1
Ractor.shareable_proc{a}
a = 2
rescue Ractor::IsolationError => e
ok += 1
end

Ractor.make_shareable(closure).call
begin
cond = false
a = 1
a = 2 if cond
Ractor.shareable_proc{a}
rescue Ractor::IsolationError => e
ok += 1
end

begin
1.times{|i|
i = 2
Ractor.shareable_proc{i}
}
rescue Ractor::IsolationError => e
ok += 1
end
}

###
Expand Down Expand Up @@ -967,7 +986,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 +1156,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
pr = Proc.new{}

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 = 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 @@ -1202,50 +1197,16 @@ module M; end
Ractor.make_shareable(ary = [C, M])
}

# Ractor.make_shareable with curried proc checks isolation of original proc
assert_equal 'isolation error', %q{
a = Object.new
orig = proc { a }
curried = orig.curry

begin
Ractor.make_shareable(curried)
rescue Ractor::IsolationError
'isolation error'
else
'no error'
end
}

# define_method() can invoke different Ractor's proc if the proc is shareable.
assert_equal '1', %q{
class C
a = 1
define_method "foo", Ractor.make_shareable(Proc.new{ a })
a = 2
define_method "foo", Ractor.shareable_proc{ a }
end

Ractor.new{ C.new.foo }.value
}

# 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
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
}

# Ractor.make_shareable(obj, copy: true) makes copied shareable object.
assert_equal '[false, false, true, true]', %q{
r = []
Expand Down Expand Up @@ -1471,42 +1432,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
Loading