ruby/test/ruby/test_ractor.rb
Alan Wu 9363231d24 test_ractor.rb: Delete unnecessary GC.stress fiddling
assert_ractor() runs in a subprocess.
2025-11-11 16:41:46 -05:00

218 lines
6.5 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
class TestRactor < Test::Unit::TestCase
def test_shareability_of_iseq_proc
assert_raise Ractor::IsolationError do
foo = []
Ractor.shareable_proc{ foo }
end
end
def test_shareability_of_method_proc
# TODO: fix with Ractor.shareable_proc/lambda
=begin
str = +""
x = str.instance_exec { proc { to_s } }
assert_unshareable(x, /Proc\'s self is not shareable/)
x = str.instance_exec { method(:to_s) }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:to_s).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:itself).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
str.freeze
x = str.instance_exec { proc { to_s } }
assert_make_shareable(x)
x = str.instance_exec { method(:to_s) }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:to_s).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:itself).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
=end
end
def test_shareability_error_uses_inspect
x = (+"").instance_exec { method(:to_s) }
def x.to_s
raise "this should not be called"
end
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
end
def test_default_thread_group
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
Warning[:experimental] = false
main_ractor_id = Thread.current.group.object_id
ractor_id = Ractor.new { Thread.current.group.object_id }.value
refute_equal main_ractor_id, ractor_id
end;
end
def test_class_instance_variables
assert_ractor(<<~'RUBY')
# Once we're in multi-ractor mode, the codepaths
# for class instance variables are a bit different.
Ractor.new {}.value
class TestClass
@a = 1
@b = 2
@c = 3
@d = 4
end
assert_equal 4, TestClass.remove_instance_variable(:@d)
assert_nil TestClass.instance_variable_get(:@d)
assert_equal 4, TestClass.instance_variable_set(:@d, 4)
assert_equal 4, TestClass.instance_variable_get(:@d)
RUBY
end
def test_struct_instance_variables
assert_ractor(<<~'RUBY')
StructIvar = Struct.new(:member) do
def initialize(*)
super
@ivar = "ivar"
end
attr_reader :ivar
end
obj = StructIvar.new("member")
obj_copy = Ractor.new { Ractor.receive }.send(obj).value
assert_equal obj.ivar, obj_copy.ivar
refute_same obj.ivar, obj_copy.ivar
assert_equal obj.member, obj_copy.member
refute_same obj.member, obj_copy.member
RUBY
end
def test_move_nested_hash_during_gc_with_yjit
assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }])
GC.stress = true
hash = { foo: { bar: "hello" }, baz: { qux: "there" } }
result = Ractor.new { Ractor.receive }.send(hash, move: true).value
assert_equal "hello", result[:foo][:bar]
assert_equal "there", result[:baz][:qux]
RUBY
end
def test_fork_raise_isolation_error
assert_ractor(<<~'RUBY')
ractor = Ractor.new do
Process.fork
rescue Ractor::IsolationError => e
e
end
assert_equal Ractor::IsolationError, ractor.value.class
RUBY
end if Process.respond_to?(:fork)
def test_require_raises_and_no_ractor_belonging_issue
assert_ractor(<<~'RUBY')
require "tempfile"
f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
f.write("raise 'uh oh'")
f.flush
err_msg = Ractor.new(f.path) do |path|
begin
require path
rescue RuntimeError => e
e.message # had confirm belonging issue here
else
nil
end
end.value
assert_equal "uh oh", err_msg
RUBY
end
def test_require_non_string
assert_ractor(<<~'RUBY')
require "tempfile"
require "pathname"
f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
f.write("")
f.flush
result = Ractor.new(f.path) do |path|
require Pathname.new(path)
"success"
end.value
assert_equal "success", result
RUBY
end
# [Bug #21398]
def test_port_receive_dnt_with_port_send
omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|mingw|darwin/
assert_ractor(<<~'RUBY', timeout: 90)
THREADS = 10
JOBS_PER_THREAD = 50
ARRAY_SIZE = 20_000
def ractor_job(job_count, array_size)
port = Ractor::Port.new
workers = (1..4).map do |i|
Ractor.new(port) do |job_port|
while job = Ractor.receive
result = job.map { |x| x * 2 }.sum
job_port.send result
end
end
end
jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
jobs.each_with_index do |job, i|
w_idx = i % 4
workers[w_idx].send(job)
end
results = []
jobs.size.times do
result = port.receive # dnt receive
results << result
end
results
end
threads = []
# creates 40 ractors (THREADSx4)
THREADS.times do
threads << Thread.new do
ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
end
end
threads.each(&:join)
RUBY
end
# [Bug #20146]
def test_max_cpu_1
assert_ractor(<<~'RUBY', args: [{ "RUBY_MAX_CPU" => "1" }])
assert_equal :ok, Ractor.new { :ok }.value
RUBY
end
def assert_make_shareable(obj)
refute Ractor.shareable?(obj), "object was already shareable"
Ractor.make_shareable(obj)
assert Ractor.shareable?(obj), "object didn't become shareable"
end
def assert_unshareable(obj, msg=nil, exception: Ractor::IsolationError)
refute Ractor.shareable?(obj), "object is already shareable"
assert_raise_with_message(exception, msg) do
Ractor.make_shareable(obj)
end
refute Ractor.shareable?(obj), "despite raising, object became shareable"
end
end