summaryrefslogtreecommitdiff
path: root/bootstraptest/test_ractor.rb
diff options
context:
space:
mode:
Diffstat (limited to 'bootstraptest/test_ractor.rb')
-rw-r--r--bootstraptest/test_ractor.rb317
1 files changed, 270 insertions, 47 deletions
diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb
index b29db7ab0e..4868810308 100644
--- a/bootstraptest/test_ractor.rb
+++ b/bootstraptest/test_ractor.rb
@@ -283,8 +283,7 @@ assert_equal 30.times.map { 'ok' }.to_s, %q{
30.times.map{|i|
test i
}
-} unless ENV['RUN_OPTS'] =~ /--jit-min-calls=5/ || # This always fails with --jit-wait --jit-min-calls=5
- (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878
+} unless (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878
# Exception for empty select
assert_match /specify at least one ractor/, %q{
@@ -501,7 +500,7 @@ assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{
rs.delete r
n
}.sort
-}
+} unless /mswin/ =~ RUBY_PLATFORM # randomly hangs on mswin https://github.com/ruby/ruby/actions/runs/3753871445/jobs/6377551069#step:20:131
# Ractor.select also support multiple take, receive and yield
assert_equal '[true, true, true]', %q{
@@ -515,9 +514,9 @@ assert_equal '[true, true, true]', %q{
end
}
received = []
- take = []
+ taken = []
yielded = []
- until rs.empty?
+ until received.size == RN && taken.size == RN && yielded.size == RN
r, v = Ractor.select(CR, *rs, yield_value: 'yield')
case r
when :receive
@@ -525,11 +524,17 @@ assert_equal '[true, true, true]', %q{
when :yield
yielded << v
else
- take << v
+ taken << v
rs.delete r
end
end
- [received.all?('sendyield'), yielded.all?(nil), take.all?('take')]
+ r = [received == ['sendyield'] * RN,
+ yielded == [nil] * RN,
+ taken == ['take'] * RN,
+ ]
+
+ STDERR.puts [received, yielded, taken].inspect
+ r
}
# multiple Ractors can send to one Ractor
@@ -623,7 +628,7 @@ assert_equal "allocator undefined for Thread", %q{
}
# send shareable and unshareable objects
-assert_equal "ok", %q{
+assert_equal "ok", <<~'RUBY', frozen_string_literal: false
echo_ractor = Ractor.new do
loop do
v = Ractor.receive
@@ -690,10 +695,10 @@ assert_equal "ok", %q{
else
results.inspect
end
-}
+RUBY
# frozen Objects are shareable
-assert_equal [false, true, false].inspect, %q{
+assert_equal [false, true, false].inspect, <<~'RUBY', frozen_string_literal: false
class C
def initialize freeze
@a = 1
@@ -716,11 +721,11 @@ assert_equal [false, true, false].inspect, %q{
results << check(C.new(true)) # false
results << check(C.new(true).freeze) # true
results << check(C.new(false).freeze) # false
-}
+RUBY
# move example2: String
# touching moved object causes an error
-assert_equal 'hello world', %q{
+assert_equal 'hello world', <<~'RUBY', frozen_string_literal: false
# move
r = Ractor.new do
obj = Ractor.receive
@@ -738,7 +743,7 @@ assert_equal 'hello world', %q{
else
raise 'unreachable'
end
-}
+RUBY
# move example2: Array
assert_equal '[0, 1]', %q{
@@ -941,7 +946,7 @@ assert_equal 'ArgumentError', %q{
}
# ivar in shareable-objects are not allowed to access from non-main Ractor
-assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", %q{
+assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false
class C
@iv = 'str'
end
@@ -952,13 +957,12 @@ assert_equal "can not get unshareable values from instance variables of classes/
end
end
-
begin
r.take
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+RUBY
# ivar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
@@ -1082,6 +1086,27 @@ assert_equal '333', %q{
a + b + c + d + e + f
}
+# moved objects have their shape properly set to original object's shape
+assert_equal '1234', %q{
+class Obj
+ attr_accessor :a, :b, :c, :d
+ def initialize
+ @a = 1
+ @b = 2
+ @c = 3
+ end
+end
+r = Ractor.new do
+ obj = receive
+ obj.d = 4
+ [obj.a, obj.b, obj.c, obj.d]
+end
+obj = Obj.new
+r.send(obj, move: true)
+values = r.take
+values.join
+}
+
# cvar in shareable-objects are not allowed to access from non-main Ractor
assert_equal 'can not access class variables from non-main Ractors', %q{
class C
@@ -1124,7 +1149,7 @@ assert_equal 'can not access class variables from non-main Ractors', %q{
}
# Getting non-shareable objects via constants by other Ractors is not allowed
-assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', %q{
+assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', <<~'RUBY', frozen_string_literal: false
class C
CONST = 'str'
end
@@ -1136,10 +1161,10 @@ assert_equal 'can not access non-shareable objects in constant C::CONST by non-m
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+ RUBY
# Constant cache should care about non-sharable constants
-assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", %q{
+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
s = str() # fill const cache
@@ -1148,10 +1173,10 @@ assert_equal "can not access non-shareable objects in constant Object::STR by no
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+RUBY
# Setting non-shareable objects into constants by other Ractors is not allowed
-assert_equal 'can not set constants with non-shareable objects by non-main Ractors', %q{
+assert_equal 'can not set constants with non-shareable objects by non-main Ractors', <<~'RUBY', frozen_string_literal: false
class C
end
r = Ractor.new do
@@ -1162,7 +1187,7 @@ assert_equal 'can not set constants with non-shareable objects by non-main Racto
rescue Ractor::RemoteError => e
e.cause.message
end
-}
+RUBY
# define_method is not allowed
assert_equal "defined with an un-shareable Proc in a different Ractor", %q{
@@ -1215,7 +1240,7 @@ assert_equal '0', %q{
}
# ObjectSpace._id2ref can not handle unshareable objects with Ractors
-assert_equal 'ok', %q{
+assert_equal 'ok', <<~'RUBY', frozen_string_literal: false
s = 'hello'
Ractor.new s.object_id do |id ;s|
@@ -1225,10 +1250,10 @@ assert_equal 'ok', %q{
:ok
end
end.take
-}
+RUBY
# Ractor.make_shareable(obj)
-assert_equal 'true', %q{
+assert_equal 'true', <<~'RUBY', frozen_string_literal: false
class C
def initialize
@a = 'foo'
@@ -1299,7 +1324,7 @@ assert_equal 'true', %q{
}
Ractor.shareable?(a)
-}
+RUBY
# Ractor.make_shareable(obj) doesn't freeze shareable objects
assert_equal 'true', %q{
@@ -1396,14 +1421,14 @@ assert_equal '[false, false, true, true]', %q{
}
# TracePoint with normal Proc should be Ractor local
-assert_equal '[4, 8]', %q{
+assert_equal '[6, 10]', %q{
rs = []
TracePoint.new(:line){|tp| rs << tp.lineno if tp.path == __FILE__}.enable do
- Ractor.new{ # line 4
+ Ractor.new{ # line 5
a = 1
b = 2
}.take
- c = 3 # line 8
+ c = 3 # line 9
end
rs
}
@@ -1471,28 +1496,13 @@ assert_equal "#{N}#{N}", %Q{
}.map{|r| r.take}.join
}
-# enc_table
-assert_equal "#{N/10}", %Q{
- Ractor.new do
- loop do
- Encoding.find("test-enc-#{rand(5_000)}").inspect
- rescue ArgumentError => e
- end
- end
-
- src = Encoding.find("UTF-8")
- #{N/10}.times{|i|
- src.replicate("test-enc-\#{i}")
- }
-}
-
# Generic ivtbl
n = N/2
assert_equal "#{n}#{n}", %Q{
2.times.map{
Ractor.new do
#{n}.times do
- obj = ''
+ obj = +''
obj.instance_variable_set("@a", 1)
obj.instance_variable_set("@b", 1)
obj.instance_variable_set("@c", 1)
@@ -1504,8 +1514,9 @@ assert_equal "#{n}#{n}", %Q{
# NameError
assert_equal "ok", %q{
+ obj = "".freeze # NameError refers the receiver indirectly
begin
- bar
+ obj.bar
rescue => err
end
begin
@@ -1541,7 +1552,7 @@ assert_equal "ok", %q{
1_000.times { idle_worker, tmp_reporter = Ractor.select(*workers) }
"ok"
-}
+} unless yjit_enabled? || rjit_enabled? # flaky
assert_equal "ok", %q{
def foo(*); ->{ super }; end
@@ -1579,4 +1590,216 @@ assert_equal "ok", %q{
end
}
+# check method cache invalidation
+assert_equal "ok", %q{
+ module M
+ def foo
+ @foo
+ end
+ end
+
+ class A
+ include M
+
+ def initialize
+ 100.times { |i| instance_variable_set(:"@var_#{i}", "bad: #{i}") }
+ @foo = 2
+ end
+ end
+
+ class B
+ include M
+
+ def initialize
+ @foo = 1
+ end
+ end
+
+ Ractor.new do
+ b = B.new
+ 100_000.times do
+ raise unless b.foo == 1
+ end
+ end
+
+ a = A.new
+ 100_000.times do
+ raise unless a.foo == 2
+ end
+
+ "ok"
+}
+
+# check method cache invalidation
+assert_equal 'true', %q{
+ class C1; def self.foo = 1; end
+ class C2; def self.foo = 2; end
+ class C3; def self.foo = 3; end
+ class C4; def self.foo = 5; end
+ class C5; def self.foo = 7; end
+ class C6; def self.foo = 11; end
+ class C7; def self.foo = 13; end
+ class C8; def self.foo = 17; end
+
+ LN = 10_000
+ RN = 10
+ CS = [C1, C2, C3, C4, C5, C6, C7, C8]
+ rs = RN.times.map{|i|
+ Ractor.new(CS.shuffle){|cs|
+ LN.times.sum{
+ cs.inject(1){|r, c| r * c.foo} # c.foo invalidates method cache entry
+ }
+ }
+ }
+
+ n = CS.inject(1){|r, c| r * c.foo} * LN
+ rs.map{|r| r.take} == Array.new(RN){n}
+}
+
+# check experimental warning
+assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{
+ Warning[:experimental] = $VERBOSE = true
+ STDERR.reopen(STDOUT)
+ eval("Ractor.new{}.take", nil, "test_ractor.rb", 1)
+}, frozen_string_literal: false
+
+# check moved object
+assert_equal 'ok', %q{
+ r = Ractor.new do
+ Ractor.receive
+ GC.start
+ :ok
+ end
+
+ obj = begin
+ raise
+ rescue => e
+ e = Marshal.load(Marshal.dump(e))
+ end
+
+ r.send obj, move: true
+ r.take
+}
+
+## Ractor::Selector
+
+# Selector#empty? returns true
+assert_equal 'true', %q{
+ skip true unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.empty?
+}
+
+# Selector#empty? returns false if there is target ractors
+assert_equal 'false', %q{
+ skip false unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.add Ractor.new{}
+ s.empty?
+}
+
+# Selector#clear removes all ractors from the waiting list
+assert_equal 'true', %q{
+ skip true unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.add Ractor.new{10}
+ s.add Ractor.new{20}
+ s.clear
+ s.empty?
+}
+
+# Selector#wait can wait multiple ractors
+assert_equal '[10, 20, true]', %q{
+ skip [10, 20, true] unless defined? Ractor::Selector
+
+ s = Ractor::Selector.new
+ s.add Ractor.new{10}
+ s.add Ractor.new{20}
+ r, v = s.wait
+ vs = []
+ vs << v
+ r, v = s.wait
+ vs << v
+ [*vs.sort, s.empty?]
+} if defined? Ractor::Selector
+
+# Selector#wait can wait multiple ractors with receiving.
+assert_equal '30', %q{
+ skip 30 unless defined? Ractor::Selector
+
+ RN = 30
+ rs = RN.times.map{
+ Ractor.new{ :v }
+ }
+ s = Ractor::Selector.new(*rs)
+
+ results = []
+ until s.empty?
+ results << s.wait
+
+ # Note that s.wait can raise an exception because other Ractors/Threads
+ # can take from the same ractors in the waiting set.
+ # In this case there is no other takers so `s.wait` doesn't raise an error.
+ end
+
+ results.size
+} if defined? Ractor::Selector
+
+# Selector#wait can support dynamic addition
+assert_equal '600', %q{
+ skip 600 unless defined? Ractor::Selector
+
+ RN = 100
+ s = Ractor::Selector.new
+ rs = RN.times.map{
+ Ractor.new{
+ Ractor.main << Ractor.new{ Ractor.yield :v3; :v4 }
+ Ractor.main << Ractor.new{ Ractor.yield :v5; :v6 }
+ Ractor.yield :v1
+ :v2
+ }
+ }
+
+ rs.each{|r| s.add(r)}
+ h = {v1: 0, v2: 0, v3: 0, v4: 0, v5: 0, v6: 0}
+
+ loop do
+ case s.wait receive: true
+ in :receive, r
+ s.add r
+ in r, v
+ h[v] += 1
+ break if h.all?{|k, v| v == RN}
+ end
+ end
+
+ h.sum{|k, v| v}
+} unless yjit_enabled? # http://ci.rvm.jp/results/trunk-yjit@ruby-sp2-docker/4466770
+
+# Selector should be GCed (free'ed) without trouble
+assert_equal 'ok', %q{
+ skip :ok unless defined? Ractor::Selector
+
+ RN = 30
+ rs = RN.times.map{
+ Ractor.new{ :v }
+ }
+ s = Ractor::Selector.new(*rs)
+ :ok
+}
+
end # if !ENV['GITHUB_WORKFLOW']
+
+# Chilled strings are not shareable
+assert_equal 'false', %q{
+ Ractor.shareable?("chilled")
+}
+
+# Chilled strings can be made shareable
+assert_equal 'true', %q{
+ shareable = Ractor.make_shareable("chilled")
+ shareable == "chilled" && Ractor.shareable?(shareable)
+}