# Ractor.current returns a current ractor assert_equal 'Ractor', %q{ Ractor.current.class } # Ractor.new returns new Ractor assert_equal 'Ractor', %q{ Ractor.new{}.class } # Ractor.new must call with a block assert_equal "must be called with a block", %q{ begin Ractor.new rescue ArgumentError => e e.message end } # A return value of a Ractor block will be a message from the Ractor. assert_equal 'ok', %q{ # join r = Ractor.new do 'ok' end r.take } # Passed arguments to Ractor.new will be a block parameter # The values are passed with Ractor-communication pass. assert_equal 'ok', %q{ # ping-pong with arg r = Ractor.new 'ok' do |msg| msg end r.take } assert_equal 'ok', %q{ # ping-pong with two args r = Ractor.new 'ping', 'pong' do |msg, msg2| [msg, msg2] end 'ok' if r.take == ['ping', 'pong'] } # Ractor#send passes an object with copy to a Ractor # and Ractor.recv in the Ractor block can receive the passed value. assert_equal 'ok', %q{ r = Ractor.new do msg = Ractor.recv end r.send 'ok' r.take } ### ### # Ractor still has several memory corruption so skip huge number of tests if ENV['GITHUB_WORKFLOW'] && ENV['GITHUB_WORKFLOW'] == 'Compilations' # ignore the follow else # Ractor.select(*ractors) receives a values from a ractors. # It is similar to select(2) and Go's select syntax. # The return value is [ch, received_value] assert_equal 'ok', %q{ # select 1 r1 = Ractor.new{'r1'} r, obj = Ractor.select(r1) 'ok' if r == r1 and obj == 'r1' } assert_equal '["r1", "r2"]', %q{ # select 2 r1 = Ractor.new{'r1'} r2 = Ractor.new{'r2'} rs = [r1, r2] as = [] r, obj = Ractor.select(*rs) rs.delete(r) as << obj r, obj = Ractor.select(*rs) as << obj as.sort #=> ["r1", "r2"] } assert_equal 'true', %q{ def test n rs = (1..n).map do |i| Ractor.new(i) do |i| "r#{i}" end end as = [] all_rs = rs.dup n.times{ r, obj = Ractor.select(*rs) as << [r, obj] rs.delete(r) } if as.map{|r, o| r.inspect}.sort == all_rs.map{|r| r.inspect}.sort && as.map{|r, o| o}.sort == (1..n).map{|i| "r#{i}"}.sort 'ok' else 'ng' end end 30.times.map{|i| test i }.all?('ok') } # Outgoing port of a ractor will be closed when the Ractor is terminated. assert_equal 'ok', %q{ r = Ractor.new do 'finish' end r.take sleep 0.1 # wait for terminate begin o = r.take rescue Ractor::ClosedError 'ok' else "ng: #{o}" end } assert_equal 'ok', %q{ r = Ractor.new do end r.take # closed sleep 0.1 # wait for terminate begin r.send(1) rescue Ractor::ClosedError 'ok' else 'ng' end } # multiple Ractors can recv (wait) from one Ractor assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ pipe = Ractor.new do loop do Ractor.yield Ractor.recv end end RN = 10 rs = RN.times.map{|i| Ractor.new pipe, i do |pipe, i| msg = pipe.take msg # ping-pong end } RN.times{|i| pipe << i } RN.times.map{ r, n = Ractor.select(*rs) rs.delete r n }.sort } # Ractor.select also support multiple take, recv and yiled assert_equal '[true, true, true]', %q{ RN = 10 CR = Ractor.current rs = (1..RN).map{ Ractor.new do CR.send 'send' + CR.take #=> 'sendyield' 'take' end } recv = [] take = [] yiel = [] until rs.empty? r, v = Ractor.select(CR, *rs, yield_value: 'yield') case r when :recv recv << v when :yield yiel << v else take << v rs.delete r end end [recv.all?('sendyield'), yiel.all?(nil), take.all?('take')] } # multiple Ractors can send to one Ractor assert_equal '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]', %q{ pipe = Ractor.new do loop do Ractor.yield Ractor.recv end end RN = 10 RN.times.map{|i| Ractor.new pipe, i do |pipe, i| pipe << i end } RN.times.map{ pipe.take }.sort } # an exception in a Ractor will be re-raised at Ractor#recv assert_equal '[RuntimeError, "ok", true]', %q{ r = Ractor.new do raise 'ok' # exception will be transferred receiver end begin r.take rescue Ractor::RemoteError => e [e.cause.class, #=> RuntimeError e.cause.message, #=> 'ok' e.ractor == r] #=> true end } # unshareable object are copied assert_equal 'false', %q{ obj = 'str'.dup r = Ractor.new obj do |msg| msg.object_id end obj.object_id == r.take } # To copy the object, now Marshal#dump is used assert_equal 'no _dump_data is defined for class Thread', %q{ obj = Thread.new{} begin r = Ractor.new obj do |msg| msg end rescue TypeError => e e.message #=> no _dump_data is defined for class Thread else 'ng' end } # send sharable and unsharable objects assert_equal "[[[1, true], [:sym, true], [:xyzzy, true], [\"frozen\", true], " \ "[(3/1), true], [(3+4i), true], [/regexp/, true], [C, true]], " \ "[[\"mutable str\", false], [[:array], false], [{:hash=>true}, false]]]", %q{ r = Ractor.new do while v = Ractor.recv Ractor.yield v end end class C end sharable_objects = [1, :sym, 'xyzzy'.to_sym, 'frozen'.freeze, 1+2r, 3+4i, /regexp/, C] sr = sharable_objects.map{|o| r << o o2 = r.take [o, o.object_id == o2.object_id] } ur = unsharable_objects = ['mutable str'.dup, [:array], {hash: true}].map{|o| r << o o2 = r.take [o, o.object_id == o2.object_id] } [sr, ur].inspect } # move example2: String # touching moved object causes an error assert_equal 'hello world', %q{ # move r = Ractor.new do obj = Ractor.recv obj << ' world' end str = 'hello' r.send str, move: true modified = r.take begin str << ' exception' # raise Ractor::MovedError rescue Ractor::MovedError modified #=> 'hello world' else raise 'unreachable' end } # move example2: Array assert_equal '[0, 1]', %q{ r = Ractor.new do ary = Ractor.recv ary << 1 end a1 = [0] r.send a1, move: true a2 = r.take begin a1 << 2 # raise Ractor::MovedError rescue Ractor::MovedError a2.inspect end } # move with yield assert_equal 'hello', %q{ r = Ractor.new do Thread.current.report_on_exception = false obj = 'hello' Ractor.yield obj, move: true obj << 'world' end str = r.take begin r.take rescue Ractor::RemoteError str #=> "hello" end } # Access to global-variables are prohibitted assert_equal 'can not access global variables $gv from non-main Ractors', %q{ $gv = 1 r = Ractor.new do $gv end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # Access to global-variables are prohibitted assert_equal 'can not access global variables $gv from non-main Ractors', %q{ r = Ractor.new do $gv = 1 end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # $stdin,out,err is Ractor local, but shared fds assert_equal 'ok', %q{ r = Ractor.new do [$stdin, $stdout, $stderr].map{|io| [io.object_id, io.fileno] } end [$stdin, $stdout, $stderr].zip(r.take){|io, (oid, fno)| raise "should not be different object" if io.object_id == oid raise "fd should be same" unless io.fileno == fno } 'ok' } # selfs are different objects assert_equal 'false', %q{ r = Ractor.new do self.object_id end r.take == self.object_id #=> false } # self is a Ractor instance assert_equal 'true', %q{ r = Ractor.new do self.object_id end r.object_id == r.take #=> true } # given block Proc will be isolated, so can not access outer variables. assert_equal 'ArgumentError', %q{ begin a = true r = Ractor.new do a end rescue => e e.class end } # ivar in sharable-objects are not allowed to access from non-main Ractor assert_equal 'can not access instance variables of classes/modules from non-main Ractors', %q{ class C @iv = 'str' end r = Ractor.new do class C p @iv end end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # ivar in sharable-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{ shared = Ractor.new{} shared.instance_variable_set(:@iv, 'str') r = Ractor.new shared do |shared| p shared.instance_variable_get(:@iv) end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # cvar in sharable-objects are not allowed to access from non-main Ractor assert_equal 'can not access class variables from non-main Ractors', %q{ class C @@cv = 'str' end r = Ractor.new do class C p @@cv end end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # Getting non-sharable objects via constants by other Ractors is not allowed assert_equal 'can not access non-sharable objects in constant C::CONST by non-main Ractor.', %q{ class C CONST = 'str' end r = Ractor.new do C::CONST end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # Setting non-sharable objects into constants by other Ractors is not allowed assert_equal 'can not set constants with non-shareable objects by non-main Ractors', %q{ class C end r = Ractor.new do C::CONST = 'str' end begin r.take rescue Ractor::RemoteError => e e.cause.message end } # Immutable Array and Hash are shareable, so it can be shared with constants assert_equal '[1000, 3]', %q{ A = Array.new(1000).freeze # [nil, ...] H = {a: 1, b: 2, c: 3}.freeze Ractor.new{ [A.size, H.size] }.take } # A Ractor can have a name assert_equal 'test-name', %q{ r = Ractor.new name: 'test-name' do end r.name } # If Ractor doesn't have a name, Ractor#name returns nil. assert_equal 'nil', %q{ r = Ractor.new do end r.name.inspect } end # if !ENV['GITHUB_WORKFLOW']