diff options
Diffstat (limited to 'bootstraptest/test_ractor.rb')
-rw-r--r-- | bootstraptest/test_ractor.rb | 317 |
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) +} |