ractor-shim 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2309 @@
1
+ # From https://github.com/ruby/ruby/blob/master/bootstraptest/test_ractor.rb
2
+
3
+ # Ractor.current returns a current ractor
4
+ assert_equal 'Ractor', %q{
5
+ Ractor.current.class
6
+ }
7
+
8
+ # Ractor.new returns new Ractor
9
+ assert_equal 'Ractor', %q{
10
+ Ractor.new{}.class
11
+ }
12
+
13
+ # Ractor.allocate is not supported
14
+ assert_equal "[:ok, :ok]", %q{
15
+ rs = []
16
+ begin
17
+ Ractor.allocate
18
+ rescue => e
19
+ rs << :ok if e.message == 'allocator undefined for Ractor'
20
+ end
21
+
22
+ begin
23
+ Ractor.new{}.dup
24
+ rescue
25
+ rs << :ok if e.message == 'allocator undefined for Ractor'
26
+ end
27
+
28
+ rs
29
+ }
30
+
31
+ # A Ractor can have a name
32
+ assert_equal 'test-name', %q{
33
+ r = Ractor.new name: 'test-name' do
34
+ end
35
+ r.name
36
+ }
37
+
38
+ # If Ractor doesn't have a name, Ractor#name returns nil.
39
+ assert_equal 'nil', %q{
40
+ r = Ractor.new do
41
+ end
42
+ r.name.inspect
43
+ }
44
+
45
+ # Raises exceptions if initialize with an invalid name
46
+ assert_equal 'ok', %q{
47
+ begin
48
+ r = Ractor.new(name: [{}]) {}
49
+ rescue TypeError => e
50
+ 'ok'
51
+ end
52
+ }
53
+
54
+ # Ractor.new must call with a block
55
+ assert_equal "must be called with a block", %q{
56
+ begin
57
+ Ractor.new
58
+ rescue ArgumentError => e
59
+ e.message
60
+ end
61
+ }
62
+
63
+ # Ractor#inspect
64
+ # Return only id and status for main ractor
65
+ assert_equal "#<Ractor:#1 running>", %q{
66
+ Ractor.current.inspect
67
+ }
68
+
69
+ # Return id, loc, and status for no-name ractor
70
+ assert_match /^#<Ractor:#([^ ]*?) .+:[0-9]+ terminated>$/, %q{
71
+ r = Ractor.new { '' }
72
+ r.join
73
+ sleep 0.1 until r.inspect =~ /terminated/
74
+ r.inspect
75
+ }
76
+
77
+ # Return id, name, loc, and status for named ractor
78
+ assert_match /^#<Ractor:#([^ ]*?) Test Ractor .+:[0-9]+ terminated>$/, %q{
79
+ r = Ractor.new(name: 'Test Ractor') { '' }
80
+ r.join
81
+ sleep 0.1 until r.inspect =~ /terminated/
82
+ r.inspect
83
+ }
84
+
85
+ # A return value of a Ractor block will be a message from the Ractor.
86
+ assert_equal 'ok', %q{
87
+ # join
88
+ r = Ractor.new do
89
+ 'ok'
90
+ end
91
+ r.value
92
+ }
93
+
94
+ # Passed arguments to Ractor.new will be a block parameter
95
+ # The values are passed with Ractor-communication pass.
96
+ assert_equal 'ok', %q{
97
+ # ping-pong with arg
98
+ r = Ractor.new 'ok' do |msg|
99
+ msg
100
+ end
101
+ r.value
102
+ }
103
+
104
+ # Pass multiple arguments to Ractor.new
105
+ assert_equal 'ok', %q{
106
+ # ping-pong with two args
107
+ r = Ractor.new 'ping', 'pong' do |msg, msg2|
108
+ [msg, msg2]
109
+ end
110
+ 'ok' if r.value == ['ping', 'pong']
111
+ }
112
+
113
+ # Ractor#send passes an object with copy to a Ractor
114
+ # and Ractor.receive in the Ractor block can receive the passed value.
115
+ assert_equal 'ok', %q{
116
+ r = Ractor.new do
117
+ msg = Ractor.receive
118
+ end
119
+ r.send 'ok'
120
+ r.value
121
+ }
122
+
123
+ # Ractor#receive_if can filter the message
124
+ assert_equal '[1, 2, 3]', %q{
125
+ ports = 3.times.map{Ractor::Port.new}
126
+
127
+ r = Ractor.new ports do |ports|
128
+ ports[0] << 3
129
+ ports[1] << 1
130
+ ports[2] << 2
131
+ end
132
+ a = []
133
+ a << ports[1].receive # 1
134
+ a << ports[2].receive # 2
135
+ a << ports[0].receive # 3
136
+ ports.each(&:close); a
137
+ }
138
+
139
+ # dtoa race condition
140
+ assert_equal '[:ok, :ok, :ok]', %q{
141
+ n = 3
142
+ n.times.map{
143
+ Ractor.new{
144
+ 10_000.times{ rand.to_s }
145
+ :ok
146
+ }
147
+ }.map(&:value)
148
+ }
149
+
150
+ assert_equal "42", %q{
151
+ a = 42
152
+ Ractor.shareable_lambda{ a }.call
153
+ }
154
+
155
+ # Ractor.make_shareable issue for locals in proc [Bug #18023]
156
+ assert_equal '[:a, :b, :c, :d, :e]', %q{
157
+ v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e
158
+ closure = Proc.new { [v1, v2, v3, v4, v5] }
159
+ Ractor.shareable_proc(&closure).call
160
+ }
161
+
162
+ # Ractor::IsolationError cases
163
+ assert_equal '3', %q{
164
+ ok = 0
165
+
166
+ begin
167
+ a = 1
168
+ Ractor.shareable_proc{a}
169
+ a = 2
170
+ rescue Ractor::IsolationError => e
171
+ ok += 1
172
+ end
173
+
174
+ begin
175
+ cond = false
176
+ a = 1
177
+ a = 2 if cond
178
+ Ractor.shareable_proc{a}
179
+ rescue Ractor::IsolationError => e
180
+ ok += 1
181
+ end
182
+
183
+ begin
184
+ 1.times{|i|
185
+ i = 2
186
+ Ractor.shareable_proc{i}
187
+ }
188
+ rescue Ractor::IsolationError => e
189
+ ok += 1
190
+ end
191
+ }
192
+
193
+ ###
194
+ ###
195
+ # Ractor still has several memory corruption so skip huge number of tests
196
+ if ENV['GITHUB_WORKFLOW'] == 'Compilations'
197
+ # ignore the follow
198
+ else
199
+
200
+ # Ractor.select with a Ractor argument
201
+ assert_equal 'ok', %q{
202
+ # select 1
203
+ r1 = Ractor.new{'r1'}
204
+ port, obj = Ractor.select(r1)
205
+ if port == r1 and obj == 'r1'
206
+ 'ok'
207
+ else
208
+ # failed
209
+ [port, obj].inspect
210
+ end
211
+ }
212
+
213
+ # Ractor.select from two ractors.
214
+ assert_equal '["r1", "r2"]', %q{
215
+ # select 2
216
+ p1 = Ractor::Port.new
217
+ p2 = Ractor::Port.new
218
+ r1 = Ractor.new(p1){|p1| p1 << 'r1'}
219
+ r2 = Ractor.new(p2){|p2| p2 << 'r2'}
220
+ ps = [p1, p2]
221
+ as = []
222
+ port, obj = Ractor.select(*ps)
223
+ ps.delete(port)
224
+ as << obj
225
+ port, obj = Ractor.select(*ps)
226
+ as << obj
227
+ as.sort #=> ["r1", "r2"]
228
+ }
229
+
230
+ # Ractor.select from multiple ractors.
231
+ assert_equal 30.times.map { 'ok' }.to_s, %q{
232
+ def test n
233
+ rs = (1..n).map do |i|
234
+ Ractor.new(i) do |i|
235
+ "r#{i}"
236
+ end
237
+ end
238
+ as = []
239
+ all_rs = rs.dup
240
+
241
+ n.times{
242
+ r, obj = Ractor.select(*rs)
243
+ as << [r, obj]
244
+ rs.delete(r)
245
+ }
246
+
247
+ if as.map{|r, o| r.object_id}.sort == all_rs.map{|r| r.object_id}.sort &&
248
+ as.map{|r, o| o}.sort == (1..n).map{|i| "r#{i}"}.sort
249
+ 'ok'
250
+ else
251
+ 'ng'
252
+ end
253
+ end
254
+
255
+ 30.times.map{|i|
256
+ test i
257
+ }
258
+ } unless (ENV.key?('TRAVIS') && ENV['TRAVIS_CPU_ARCH'] == 'arm64') # https://bugs.ruby-lang.org/issues/17878
259
+
260
+ # Exception for empty select
261
+ assert_match /specify at least one ractor/, %q{
262
+ begin
263
+ Ractor.select
264
+ rescue ArgumentError => e
265
+ e.message
266
+ end
267
+ }
268
+
269
+ # Raise Ractor::ClosedError when try to send into a terminated ractor
270
+ assert_equal 'ok', %q{
271
+ r = Ractor.new do
272
+ end
273
+
274
+ r.join # closed
275
+ sleep 0.1 until r.inspect =~ /terminated/
276
+
277
+ begin
278
+ r.send(1)
279
+ rescue Ractor::ClosedError
280
+ 'ok'
281
+ else
282
+ 'ng'
283
+ end
284
+ }
285
+
286
+ # Can mix with Thread#interrupt and Ractor#join [Bug #17366]
287
+ assert_equal 'err', %q{
288
+ Ractor.new do
289
+ t = Thread.current
290
+ begin
291
+ Thread.new{ t.raise "err" }.join
292
+ rescue => e
293
+ e.message
294
+ end
295
+ end.value
296
+ }
297
+
298
+ # Killed Ractor's thread yields nil
299
+ assert_equal 'nil', %q{
300
+ Ractor.new{
301
+ t = Thread.current
302
+ Thread.new{ t.kill }.join
303
+ }.value.inspect #=> nil
304
+ }
305
+
306
+ # Raise Ractor::ClosedError when try to send into a ractor with closed default port
307
+ assert_equal 'ok', %q{
308
+ r = Ractor.new {
309
+ Ractor.current.close
310
+ Ractor.main << :ok
311
+ Ractor.receive
312
+ }
313
+
314
+ Ractor.receive # wait for ok
315
+
316
+ begin
317
+ r.send(1)
318
+ rescue Ractor::ClosedError
319
+ 'ok'
320
+ else
321
+ 'ng'
322
+ end
323
+ }
324
+
325
+ # Ractor.main returns main ractor
326
+ assert_equal 'true', %q{
327
+ Ractor.new{
328
+ Ractor.main
329
+ }.value == Ractor.current
330
+ }
331
+
332
+ # a ractor with closed outgoing port should terminate
333
+ assert_equal 'ok', %q{
334
+ Ractor.new do
335
+ Ractor.current.close
336
+ end
337
+
338
+ true until Ractor.count == 1
339
+ :ok
340
+ }
341
+
342
+ # an exception in a Ractor main thread will be re-raised at Ractor#receive
343
+ assert_equal '[RuntimeError, "ok", true]', %q{
344
+ r = Ractor.new do
345
+ raise 'ok' # exception will be transferred receiver
346
+ end
347
+ begin
348
+ r.join
349
+ rescue Ractor::RemoteError => e
350
+ [e.cause.class, #=> RuntimeError
351
+ e.cause.message, #=> 'ok'
352
+ e.ractor == r] #=> true
353
+ end
354
+ }
355
+
356
+ # an exception in a Ractor will be re-raised at Ractor#value
357
+ assert_equal '[RuntimeError, "ok", true]', %q{
358
+ r = Ractor.new do
359
+ raise 'ok' # exception will be transferred receiver
360
+ end
361
+ begin
362
+ r.value
363
+ rescue Ractor::RemoteError => e
364
+ [e.cause.class, #=> RuntimeError
365
+ e.cause.message, #=> 'ok'
366
+ e.ractor == r] #=> true
367
+ end
368
+ }
369
+
370
+ # an exception in a Ractor non-main thread will not be re-raised at Ractor#receive
371
+ assert_equal 'ok', %q{
372
+ r = Ractor.new do
373
+ Thread.new do
374
+ raise 'ng'
375
+ end
376
+ sleep 0.1
377
+ 'ok'
378
+ end
379
+ r.value
380
+ }
381
+
382
+ # SystemExit from a Ractor is re-raised
383
+ # [Bug #21505]
384
+ assert_equal '[SystemExit, "exit", true]', %q{
385
+ r = Ractor.new { exit }
386
+ begin
387
+ r.value
388
+ rescue Ractor::RemoteError => e
389
+ [e.cause.class, #=> RuntimeError
390
+ e.cause.message, #=> 'ok'
391
+ e.ractor == r] #=> true
392
+ end
393
+ }
394
+
395
+ # SystemExit from a Thread inside a Ractor is re-raised
396
+ # [Bug #21505]
397
+ assert_equal '[SystemExit, "exit", true]', %q{
398
+ r = Ractor.new { Thread.new { exit }.join }
399
+ begin
400
+ r.value
401
+ rescue Ractor::RemoteError => e
402
+ [e.cause.class, #=> RuntimeError
403
+ e.cause.message, #=> 'ok'
404
+ e.ractor == r] #=> true
405
+ end
406
+ }
407
+
408
+ # threads in a ractor will killed
409
+ assert_equal '{ok: 3}', %q{
410
+ Ractor.new Ractor.current do |main|
411
+ q = Thread::Queue.new
412
+ Thread.new do
413
+ q << true
414
+ loop{}
415
+ ensure
416
+ main << :ok
417
+ end
418
+
419
+ Thread.new do
420
+ q << true
421
+ while true
422
+ end
423
+ ensure
424
+ main << :ok
425
+ end
426
+
427
+ Thread.new do
428
+ q << true
429
+ sleep 1
430
+ ensure
431
+ main << :ok
432
+ end
433
+
434
+ # wait for the start of all threads
435
+ 3.times{q.pop}
436
+ end
437
+
438
+ 3.times.map{Ractor.receive}.tally
439
+ } # unless yjit_enabled? # YJIT: `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec()
440
+
441
+ # unshareable object are copied
442
+ assert_equal 'false', %q{
443
+ obj = 'str'.dup
444
+ r = Ractor.new obj do |msg|
445
+ msg.object_id
446
+ end
447
+
448
+ obj.object_id == r.value
449
+ }
450
+
451
+ # To copy the object, now Marshal#dump is used
452
+ assert_equal "allocator undefined for Thread", %q{
453
+ obj = Thread.new{}
454
+ begin
455
+ r = Ractor.new obj do |msg|
456
+ msg
457
+ end
458
+ rescue TypeError => e
459
+ e.message #=> no _dump_data is defined for class Thread
460
+ else
461
+ 'ng'
462
+ end
463
+ }
464
+
465
+ # send shareable and unshareable objects
466
+ assert_equal "ok", <<~'RUBY', frozen_string_literal: false
467
+ port = Ractor::Port.new
468
+ echo_ractor = Ractor.new port do |port|
469
+ loop do
470
+ v = Ractor.receive
471
+ port << v
472
+ end
473
+ end
474
+
475
+ class C; end
476
+ module M; end
477
+ S = Struct.new(:a, :b, :c, :d)
478
+
479
+ shareable_objects = [
480
+ true,
481
+ false,
482
+ nil,
483
+ 1,
484
+ 1.1, # Float
485
+ 1+2r, # Rational
486
+ 3+4i, # Complex
487
+ 2**128, # Bignum
488
+ :sym, # Symbol
489
+ 'xyzzy'.to_sym, # dynamic symbol
490
+ 'frozen'.freeze, # frozen String
491
+ /regexp/, # regexp literal
492
+ /reg{true}exp/.freeze, # frozen dregexp
493
+ [1, 2].freeze, # frozen Array which only refers to shareable
494
+ {a: 1}.freeze, # frozen Hash which only refers to shareable
495
+ [{a: 1}.freeze, 'str'.freeze].freeze, # nested frozen container
496
+ S.new(1, 2).freeze, # frozen Struct
497
+ S.new(1, 2, 3, 4).freeze, # frozen Struct
498
+ (1..2), # Range on Struct
499
+ (1..), # Range on Struct
500
+ (..1), # Range on Struct
501
+ C, # class
502
+ M, # module
503
+ Ractor.current, # Ractor
504
+ ]
505
+
506
+ unshareable_objects = [
507
+ 'mutable str'.dup,
508
+ [:array],
509
+ {hash: true},
510
+ S.new(1, 2),
511
+ S.new(1, 2, 3, 4),
512
+ S.new("a", 2).freeze, # frozen, but refers to an unshareable object
513
+ ]
514
+
515
+ results = []
516
+
517
+ shareable_objects.map{|o|
518
+ echo_ractor << o
519
+ o2 = port.receive
520
+ results << "#{o} is copied" unless o.object_id == o2.object_id
521
+ }
522
+
523
+ unshareable_objects.map{|o|
524
+ echo_ractor << o
525
+ o2 = port.receive
526
+ results << "#{o.inspect} is not copied" if o.object_id == o2.object_id
527
+ }
528
+
529
+ if results.empty?
530
+ port.close; echo_ractor.close; :ok
531
+ else
532
+ results.inspect
533
+ end
534
+ RUBY
535
+
536
+ # frozen Objects are shareable
537
+ assert_equal [false, true, false].inspect, <<~'RUBY', frozen_string_literal: false
538
+ class C
539
+ def initialize freeze
540
+ @a = 1
541
+ @b = :sym
542
+ @c = 'frozen_str'
543
+ @c.freeze if freeze
544
+ @d = true
545
+ end
546
+ end
547
+
548
+ def check obj1
549
+ obj2 = Ractor.new obj1 do |obj|
550
+ obj
551
+ end.value
552
+
553
+ obj1.object_id == obj2.object_id
554
+ end
555
+
556
+ results = []
557
+ results << check(C.new(true)) # false
558
+ results << check(C.new(true).freeze) # true
559
+ results << check(C.new(false).freeze) # false
560
+ RUBY
561
+
562
+ # move example2: String
563
+ # touching moved object causes an error
564
+ assert_equal 'hello world', <<~'RUBY', frozen_string_literal: false
565
+ # move
566
+ r = Ractor.new do
567
+ obj = Ractor.receive
568
+ obj << ' world'
569
+ end
570
+
571
+ str = 'hello'
572
+ r.send str, move: true
573
+ modified = r.value
574
+
575
+ begin
576
+ str << ' exception' # raise Ractor::MovedError
577
+ rescue Ractor::MovedError
578
+ modified #=> 'hello world'
579
+ else
580
+ raise 'unreachable'
581
+ end
582
+ RUBY
583
+
584
+ # move example2: Array
585
+ assert_equal '[0, 1]', %q{
586
+ r = Ractor.new do
587
+ ary = Ractor.receive
588
+ ary << 1
589
+ end
590
+
591
+ a1 = [0]
592
+ r.send a1, move: true
593
+ a2 = r.value
594
+ begin
595
+ a1 << 2 # raise Ractor::MovedError
596
+ rescue Ractor::MovedError
597
+ a2.inspect
598
+ end
599
+ }
600
+
601
+ # unshareable frozen objects should still be frozen in new ractor after move
602
+ assert_equal 'true', %q{
603
+ r = Ractor.new do
604
+ obj = receive
605
+ { frozen: obj.frozen? }
606
+ end
607
+ obj = [Object.new].freeze
608
+ r.send(obj, move: true)
609
+ r.value[:frozen]
610
+ }
611
+
612
+ # Access to global-variables are prohibited (read)
613
+ assert_equal 'can not access global variable $gv from non-main Ractor', %q{
614
+ $gv = 1
615
+ r = Ractor.new do
616
+ $gv
617
+ end
618
+
619
+ begin
620
+ r.join
621
+ rescue Ractor::RemoteError => e
622
+ e.cause.message
623
+ end
624
+ }
625
+
626
+ # Access to global-variables are prohibited (write)
627
+ assert_equal 'can not access global variable $gv from non-main Ractor', %q{
628
+ r = Ractor.new do
629
+ $gv = 1
630
+ end
631
+
632
+ begin
633
+ r.join
634
+ rescue Ractor::RemoteError => e
635
+ e.cause.message
636
+ end
637
+ }
638
+
639
+ # $stdin,out,err is Ractor local, but shared fds
640
+ assert_equal 'ok', %q{
641
+ r = Ractor.new do
642
+ [$stdin, $stdout, $stderr].map{|io|
643
+ [io.object_id, io.fileno]
644
+ }
645
+ end
646
+
647
+ [$stdin, $stdout, $stderr].zip(r.value){|io, (oid, fno)|
648
+ raise "should not be different object" if io.object_id == oid
649
+ raise "fd should be same" unless io.fileno == fno
650
+ }
651
+ 'ok'
652
+ }
653
+
654
+ # $stdin,out,err belong to Ractor
655
+ assert_equal 'ok', %q{
656
+ r = Ractor.new do
657
+ $stdin.itself
658
+ $stdout.itself
659
+ $stderr.itself
660
+ 'ok'
661
+ end
662
+
663
+ r.value
664
+ }
665
+
666
+ # $DEBUG, $VERBOSE are Ractor local
667
+ assert_equal 'true', %q{
668
+ $DEBUG = true
669
+ $VERBOSE = true
670
+
671
+ def ractor_local_globals
672
+ /a(b)(c)d/ =~ 'abcd' # for $~
673
+ `echo foo` unless /solaris/ =~ RUBY_PLATFORM
674
+
675
+ {
676
+ # ractor-local (derived from created ractor): debug
677
+ '$DEBUG' => $DEBUG,
678
+ '$-d' => $-d,
679
+
680
+ # ractor-local (derived from created ractor): verbose
681
+ '$VERBOSE' => $VERBOSE,
682
+ '$-w' => $-w,
683
+ '$-W' => $-W,
684
+ '$-v' => $-v,
685
+
686
+ # process-local (readonly): other commandline parameters
687
+ '$-p' => $-p,
688
+ '$-l' => $-l,
689
+ '$-a' => $-a,
690
+
691
+ # process-local (readonly): getpid
692
+ '$$' => $$,
693
+
694
+ # thread local: process result
695
+ '$?' => $?,
696
+
697
+ # scope local: match
698
+ '$~' => $~.inspect,
699
+ '$&' => $&,
700
+ '$`' => $`,
701
+ '$\'' => $',
702
+ '$+' => $+,
703
+ '$1' => $1,
704
+
705
+ # scope local: last line
706
+ '$_' => $_,
707
+
708
+ # scope local: last backtrace
709
+ '$@' => $@,
710
+ '$!' => $!,
711
+
712
+ # ractor local: stdin, out, err
713
+ '$stdin' => $stdin.inspect,
714
+ '$stdout' => $stdout.inspect,
715
+ '$stderr' => $stderr.inspect,
716
+ }
717
+ end
718
+
719
+ h = Ractor.new do
720
+ ractor_local_globals
721
+ end.value
722
+ ractor_local_globals == h #=> true
723
+ }
724
+
725
+ # selfs are different objects
726
+ assert_equal 'false', %q{
727
+ r = Ractor.new do
728
+ self.object_id
729
+ end
730
+ ret = r.value
731
+ ret == self.object_id
732
+ }
733
+
734
+ # self is a Ractor instance
735
+ assert_equal 'true', %q{
736
+ r = Ractor.new do
737
+ self.object_id
738
+ end
739
+ ret = r.value
740
+ if r.object_id == ret #=> true
741
+ true
742
+ else
743
+ raise [ret, r.object_id].inspect
744
+ end
745
+ }
746
+
747
+ # given block Proc will be isolated, so can not access outer variables.
748
+ assert_equal 'ArgumentError', %q{
749
+ begin
750
+ a = true
751
+ r = Ractor.new do
752
+ a
753
+ end
754
+ rescue => e
755
+ e.class
756
+ end
757
+ }
758
+
759
+ # ivar in shareable-objects are not allowed to access from non-main Ractor
760
+ assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors", <<~'RUBY', frozen_string_literal: false
761
+ class C
762
+ @iv = 'str'
763
+ end
764
+
765
+ r = Ractor.new do
766
+ class C
767
+ p @iv
768
+ end
769
+ end
770
+
771
+ begin
772
+ r.value
773
+ rescue Ractor::RemoteError => e
774
+ e.cause.message
775
+ end
776
+ RUBY
777
+
778
+ # ivar in shareable-objects are not allowed to access from non-main Ractor
779
+ assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
780
+ shared = Ractor.new{}
781
+ shared.instance_variable_set(:@iv, 'str')
782
+
783
+ r = Ractor.new shared do |shared|
784
+ p shared.instance_variable_get(:@iv)
785
+ end
786
+
787
+ begin
788
+ r.value
789
+ rescue Ractor::RemoteError => e
790
+ e.cause.message
791
+ end
792
+ }
793
+
794
+ # ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (get)
795
+ assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
796
+ class Ractor
797
+ def setup
798
+ @foo = ''
799
+ end
800
+
801
+ def foo
802
+ @foo
803
+ end
804
+ end
805
+
806
+ shared = Ractor.new{}
807
+ shared.setup
808
+
809
+ r = Ractor.new shared do |shared|
810
+ p shared.foo
811
+ end
812
+
813
+ begin
814
+ r.value
815
+ rescue Ractor::RemoteError => e
816
+ e.cause.message
817
+ end
818
+ }
819
+
820
+ # ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (set)
821
+ assert_equal 'can not access instance variables of shareable objects from non-main Ractors', %q{
822
+ class Ractor
823
+ def setup
824
+ @foo = ''
825
+ end
826
+ end
827
+
828
+ shared = Ractor.new{}
829
+
830
+ r = Ractor.new shared do |shared|
831
+ p shared.setup
832
+ end
833
+
834
+ begin
835
+ r.value
836
+ rescue Ractor::RemoteError => e
837
+ e.cause.message
838
+ end
839
+ }
840
+
841
+ # But a shareable object is frozen, it is allowed to access ivars from non-main Ractor
842
+ assert_equal '11', %q{
843
+ [Object.new, [], ].map{|obj|
844
+ obj.instance_variable_set('@a', 1)
845
+ Ractor.make_shareable obj = obj.freeze
846
+
847
+ Ractor.new obj do |obj|
848
+ obj.instance_variable_get('@a')
849
+ end.value.to_s
850
+ }.join
851
+ }
852
+
853
+ # and instance variables of classes/modules are accessible if they refer shareable objects
854
+ assert_equal '333', %q{
855
+ class C
856
+ @int = 1
857
+ @str = '-1000'.dup
858
+ @fstr = '100'.freeze
859
+
860
+ def self.int = @int
861
+ def self.str = @str
862
+ def self.fstr = @fstr
863
+ end
864
+
865
+ module M
866
+ @int = 2
867
+ @str = '-2000'.dup
868
+ @fstr = '200'.freeze
869
+
870
+ def self.int = @int
871
+ def self.str = @str
872
+ def self.fstr = @fstr
873
+ end
874
+
875
+ a = Ractor.new{ C.int }.value
876
+ b = Ractor.new do
877
+ C.str.to_i
878
+ rescue Ractor::IsolationError
879
+ 10
880
+ end.value
881
+ c = Ractor.new do
882
+ C.fstr.to_i
883
+ end.value
884
+
885
+ d = Ractor.new{ M.int }.value
886
+ e = Ractor.new do
887
+ M.str.to_i
888
+ rescue Ractor::IsolationError
889
+ 20
890
+ end.value
891
+ f = Ractor.new do
892
+ M.fstr.to_i
893
+ end.value
894
+
895
+
896
+ # 1 + 10 + 100 + 2 + 20 + 200
897
+ a + b + c + d + e + f
898
+ }
899
+
900
+ assert_equal '["instance-variable", "instance-variable", nil]', %q{
901
+ class C
902
+ @iv1 = ""
903
+ @iv2 = 42
904
+ def self.iv1; defined?(@iv1); end # "instance-variable"
905
+ def self.iv2; defined?(@iv2); end # "instance-variable"
906
+ def self.iv3; defined?(@iv3); end # nil
907
+ end
908
+
909
+ Ractor.new{
910
+ [C.iv1, C.iv2, C.iv3]
911
+ }.value
912
+ }
913
+
914
+ # moved objects have their shape properly set to original object's shape
915
+ assert_equal '1234', %q{
916
+ class Obj
917
+ attr_accessor :a, :b, :c, :d
918
+ def initialize
919
+ @a = 1
920
+ @b = 2
921
+ @c = 3
922
+ end
923
+ end
924
+ r = Ractor.new do
925
+ obj = receive
926
+ obj.d = 4
927
+ [obj.a, obj.b, obj.c, obj.d]
928
+ end
929
+ obj = Obj.new
930
+ r.send(obj, move: true)
931
+ values = r.value
932
+ values.join
933
+ }
934
+
935
+ # cvar in shareable-objects are not allowed to access from non-main Ractor
936
+ assert_equal 'can not access class variables from non-main Ractors', %q{
937
+ class C
938
+ @@cv = 'str'
939
+ end
940
+
941
+ r = Ractor.new do
942
+ class C
943
+ p @@cv
944
+ end
945
+ end
946
+
947
+ begin
948
+ r.join
949
+ rescue Ractor::RemoteError => e
950
+ e.cause.message
951
+ end
952
+ }
953
+
954
+ # also cached cvar in shareable-objects are not allowed to access from non-main Ractor
955
+ assert_equal 'can not access class variables from non-main Ractors', %q{
956
+ class C
957
+ @@cv = 'str'
958
+ def self.cv
959
+ @@cv
960
+ end
961
+ end
962
+
963
+ C.cv # cache
964
+
965
+ r = Ractor.new do
966
+ C.cv
967
+ end
968
+
969
+ begin
970
+ r.join
971
+ rescue Ractor::RemoteError => e
972
+ e.cause.message
973
+ end
974
+ }
975
+
976
+ # Getting non-shareable objects via constants by other Ractors is not allowed
977
+ assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', <<~'RUBY', frozen_string_literal: false
978
+ class C
979
+ CONST = 'str'
980
+ end
981
+ r = Ractor.new do
982
+ C::CONST
983
+ end
984
+ begin
985
+ r.join
986
+ rescue Ractor::RemoteError => e
987
+ e.cause.message
988
+ end
989
+ RUBY
990
+
991
+ # Constant cache should care about non-shareable constants
992
+ assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false
993
+ STR = "hello"
994
+ def str; STR; end
995
+ s = str() # fill const cache
996
+ begin
997
+ Ractor.new{ str() }.join
998
+ rescue Ractor::RemoteError => e
999
+ e.cause.message
1000
+ end
1001
+ RUBY
1002
+
1003
+ # Setting non-shareable objects into constants by other Ractors is not allowed
1004
+ assert_equal 'can not set constants with non-shareable objects by non-main Ractors', <<~'RUBY', frozen_string_literal: false
1005
+ class C
1006
+ end
1007
+ r = Ractor.new do
1008
+ C::CONST = 'str'
1009
+ end
1010
+ begin
1011
+ r.join
1012
+ rescue Ractor::RemoteError => e
1013
+ e.cause.message
1014
+ end
1015
+ RUBY
1016
+
1017
+ # define_method is not allowed
1018
+ assert_equal "defined with an un-shareable Proc in a different Ractor", %q{
1019
+ str = "foo"
1020
+ define_method(:buggy){|i| str << "#{i}"}
1021
+ begin
1022
+ Ractor.new{buggy(10)}.join
1023
+ rescue => e
1024
+ e.cause.message
1025
+ end
1026
+ }
1027
+
1028
+ # Immutable Array and Hash are shareable, so it can be shared with constants
1029
+ assert_equal '[1000, 3]', %q{
1030
+ A = Array.new(1000).freeze # [nil, ...]
1031
+ H = {a: 1, b: 2, c: 3}.freeze
1032
+
1033
+ Ractor.new{ [A.size, H.size] }.value
1034
+ }
1035
+
1036
+ # Ractor.count
1037
+ assert_equal '[1, 4, 3, 2, 1]', %q{
1038
+ counts = []
1039
+ counts << Ractor.count
1040
+ ractors = (1..3).map { Ractor.new { Ractor.receive } }
1041
+ counts << Ractor.count
1042
+
1043
+ ractors[0].send('End 0').join
1044
+ sleep 0.1 until ractors[0].inspect =~ /terminated/
1045
+ counts << Ractor.count
1046
+
1047
+ ractors[1].send('End 1').join
1048
+ sleep 0.1 until ractors[1].inspect =~ /terminated/
1049
+ counts << Ractor.count
1050
+
1051
+ ractors[2].send('End 2').join
1052
+ sleep 0.1 until ractors[2].inspect =~ /terminated/
1053
+ counts << Ractor.count
1054
+
1055
+ counts.inspect
1056
+ }
1057
+
1058
+ # ObjectSpace.each_object can not handle unshareable objects with Ractors
1059
+ assert_equal '0', %q{
1060
+ Ractor.new{
1061
+ n = 0
1062
+ ObjectSpace.each_object{|o| n += 1 unless Ractor.shareable?(o)}
1063
+ n
1064
+ }.value
1065
+ }
1066
+
1067
+ # ObjectSpace._id2ref can not handle unshareable objects with Ractors
1068
+ assert_equal 'ok', <<~'RUBY', frozen_string_literal: false
1069
+ s = 'hello'
1070
+
1071
+ Ractor.new s.object_id do |id ;s|
1072
+ begin
1073
+ s = ObjectSpace._id2ref(id)
1074
+ rescue => e
1075
+ :ok
1076
+ end
1077
+ end.value
1078
+ RUBY
1079
+
1080
+ # Ractor.make_shareable(obj)
1081
+ assert_equal 'true', <<~'RUBY', frozen_string_literal: false
1082
+ class C
1083
+ def initialize
1084
+ @a = 'foo'
1085
+ @b = 'bar'
1086
+ end
1087
+
1088
+ def freeze
1089
+ @c = [:freeze_called]
1090
+ super
1091
+ end
1092
+
1093
+ attr_reader :a, :b, :c
1094
+ end
1095
+ S = Struct.new(:s1, :s2)
1096
+ str = "hello"
1097
+ str.instance_variable_set("@iv", "hello")
1098
+ /a/ =~ 'a'
1099
+ m = $~
1100
+ class N < Numeric
1101
+ def /(other)
1102
+ 1
1103
+ end
1104
+ end
1105
+ ary = []; ary << ary
1106
+
1107
+ a = [[1, ['2', '3']],
1108
+ {Object.new => "hello"},
1109
+ C.new,
1110
+ S.new("x", "y"),
1111
+ ("a".."b"),
1112
+ str,
1113
+ ary, # cycle
1114
+ /regexp/,
1115
+ /#{'r'.upcase}/,
1116
+ m,
1117
+ Complex(N.new,0),
1118
+ Rational(N.new,0),
1119
+ true,
1120
+ false,
1121
+ nil,
1122
+ 1, 1.2, 1+3r, 1+4i, # Numeric
1123
+ ]
1124
+ Ractor.make_shareable(a)
1125
+
1126
+ # check all frozen
1127
+ a.each{|o|
1128
+ raise o.inspect unless o.frozen?
1129
+
1130
+ case o
1131
+ when C
1132
+ raise o.a.inspect unless o.a.frozen?
1133
+ raise o.b.inspect unless o.b.frozen?
1134
+ raise o.c.inspect unless o.c.frozen? && o.c == [:freeze_called]
1135
+ when Rational
1136
+ raise o.numerator.inspect unless o.numerator.frozen?
1137
+ when Complex
1138
+ raise o.real.inspect unless o.real.frozen?
1139
+ when Array
1140
+ if o[0] == 1
1141
+ raise o[1][1].inspect unless o[1][1].frozen?
1142
+ end
1143
+ when Hash
1144
+ o.each{|k, v|
1145
+ raise k.inspect unless k.frozen?
1146
+ raise v.inspect unless v.frozen?
1147
+ }
1148
+ end
1149
+ }
1150
+
1151
+ Ractor.shareable?(a)
1152
+ RUBY
1153
+
1154
+ # Ractor.make_shareable(obj) doesn't freeze shareable objects
1155
+ assert_equal 'true', %q{
1156
+ r = Ractor.new{}
1157
+ Ractor.make_shareable(a = [r])
1158
+ [a.frozen?, a[0].frozen?] == [true, false]
1159
+ }
1160
+
1161
+ # Ractor.make_shareable(a_proc) is not supported now.
1162
+ assert_equal 'true', %q{
1163
+ pr = Proc.new{}
1164
+
1165
+ begin
1166
+ Ractor.make_shareable(pr)
1167
+ rescue Ractor::Error
1168
+ true
1169
+ else
1170
+ false
1171
+ end
1172
+ }
1173
+
1174
+ # Ractor.shareable?(recursive_objects)
1175
+ assert_equal '[false, false]', %q{
1176
+ y = []
1177
+ x = [y, {}].freeze
1178
+ y << x
1179
+ y.freeze
1180
+ [Ractor.shareable?(x), Ractor.shareable?(y)]
1181
+ }
1182
+
1183
+ # Ractor.make_shareable(recursive_objects)
1184
+ assert_equal '[:ok, false, false]', %q{
1185
+ o = Object.new
1186
+ def o.freeze; raise; end
1187
+ y = []
1188
+ x = [y, o].freeze
1189
+ y << x
1190
+ y.freeze
1191
+ [(Ractor.make_shareable(x) rescue :ok), Ractor.shareable?(x), Ractor.shareable?(y)]
1192
+ }
1193
+
1194
+ # Ractor.make_shareable with Class/Module
1195
+ assert_equal '[C, M]', %q{
1196
+ class C; end
1197
+ module M; end
1198
+
1199
+ Ractor.make_shareable(ary = [C, M])
1200
+ }
1201
+
1202
+ # define_method() can invoke different Ractor's proc if the proc is shareable.
1203
+ assert_equal '1', %q{
1204
+ class C
1205
+ a = 1
1206
+ define_method "foo", Ractor.shareable_proc{ a }
1207
+ end
1208
+
1209
+ Ractor.new{ C.new.foo }.value
1210
+ }
1211
+
1212
+ # Ractor.make_shareable(obj, copy: true) makes copied shareable object.
1213
+ assert_equal '[false, false, true, true]', %q{
1214
+ r = []
1215
+ o1 = [1, 2, ["3"]]
1216
+
1217
+ o2 = Ractor.make_shareable(o1, copy: true)
1218
+ r << Ractor.shareable?(o1) # false
1219
+ r << (o1.object_id == o2.object_id) # false
1220
+
1221
+ o3 = Ractor.make_shareable(o1)
1222
+ r << Ractor.shareable?(o1) # true
1223
+ r << (o1.object_id == o3.object_id) # false
1224
+ r
1225
+ }
1226
+
1227
+ # TracePoint with normal Proc should be Ractor local
1228
+ assert_equal '[6, 10]', %q{
1229
+ rs = []
1230
+ TracePoint.new(:line){|tp| rs << tp.lineno if tp.path == __FILE__}.enable do
1231
+ Ractor.new{ # line 5
1232
+ a = 1
1233
+ b = 2
1234
+ }.value
1235
+ c = 3 # line 9
1236
+ end
1237
+ rs
1238
+ }
1239
+
1240
+ # Ractor deep copies frozen objects (ary)
1241
+ assert_equal '[true, false]', %q{
1242
+ Ractor.new([[]].freeze) { |ary|
1243
+ [ary.frozen?, ary.first.frozen? ]
1244
+ }.value
1245
+ }
1246
+
1247
+ # Ractor deep copies frozen objects (str)
1248
+ assert_equal '[true, false]', %q{
1249
+ s = String.new.instance_eval { @x = []; freeze}
1250
+ Ractor.new(s) { |s|
1251
+ [s.frozen?, s.instance_variable_get(:@x).frozen?]
1252
+ }.value
1253
+ }
1254
+
1255
+ # Can not trap with not isolated Proc on non-main ractor
1256
+ assert_equal '[:ok, :ok]', %q{
1257
+ a = []
1258
+ Ractor.new{
1259
+ trap(:INT){p :ok}
1260
+ }.join
1261
+ a << :ok
1262
+
1263
+ begin
1264
+ Ractor.new{
1265
+ s = 'str'
1266
+ trap(:INT){p s}
1267
+ }.join
1268
+ rescue => Ractor::RemoteError
1269
+ a << :ok
1270
+ end
1271
+ }
1272
+
1273
+ # Ractor.select is interruptible
1274
+ assert_normal_exit %q{
1275
+ trap(:INT) do
1276
+ exit
1277
+ end
1278
+
1279
+ r = Ractor.new do
1280
+ loop do
1281
+ sleep 1
1282
+ end
1283
+ end
1284
+
1285
+ Thread.new do
1286
+ sleep 0.5
1287
+ Process.kill(:INT, Process.pid)
1288
+ end
1289
+ Ractor.select(r)
1290
+ }
1291
+
1292
+ # Ractor-local storage
1293
+ assert_equal '[nil, "b", "a"]', %q{
1294
+ ans = []
1295
+ Ractor.current[:key] = 'a'
1296
+ r = Ractor.new{
1297
+ Ractor.main << self[:key]
1298
+ self[:key] = 'b'
1299
+ self[:key]
1300
+ }
1301
+ ans << Ractor.receive
1302
+ ans << r.value
1303
+ ans << Ractor.current[:key]
1304
+ }
1305
+
1306
+ # Ractor-local storage with Thread inheritance of current Ractor
1307
+ assert_equal '1', %q{
1308
+ N = 1_000
1309
+ Ractor.new{
1310
+ a = []
1311
+ 1_000.times.map{|i|
1312
+ Thread.new(i){|i|
1313
+ Thread.pass if i < N
1314
+ a << Ractor.store_if_absent(:i){ i }
1315
+ a << Ractor.current[:i]
1316
+ }
1317
+ }.each(&:join)
1318
+ a.uniq.size
1319
+ }.value
1320
+ }
1321
+
1322
+ # Ractor-local storage
1323
+ assert_equal '2', %q{
1324
+ Ractor.new {
1325
+ fails = 0
1326
+ begin
1327
+ Ractor.main[:key] # cannot get ractor local storage from non-main ractor
1328
+ rescue => e
1329
+ fails += 1 if e.message =~ /Cannot get ractor local/
1330
+ end
1331
+ begin
1332
+ Ractor.main[:key] = 'val'
1333
+ rescue => e
1334
+ fails += 1 if e.message =~ /Cannot set ractor local/
1335
+ end
1336
+ fails
1337
+ }.value
1338
+ }
1339
+
1340
+ ###
1341
+ ### Synchronization tests
1342
+ ###
1343
+
1344
+ N = 100_000
1345
+
1346
+ # fstring pool
1347
+ assert_equal "#{N}#{N}", %Q{
1348
+ N = #{N}
1349
+ 2.times.map{
1350
+ Ractor.new{
1351
+ N.times{|i| -(i.to_s)}
1352
+ }
1353
+ }.map{|r| r.value}.join
1354
+ }
1355
+
1356
+ # fstring pool 2
1357
+ assert_equal "ok", %Q{
1358
+ N = #{N}
1359
+ a, b = 2.times.map{
1360
+ Ractor.new{
1361
+ N.times.map{|i| -(i.to_s)}
1362
+ }
1363
+ }.map{|r| r.value}
1364
+ N.times do |i|
1365
+ unless a[i].equal?(b[i])
1366
+ raise [a[i], b[i]].inspect
1367
+ end
1368
+ end
1369
+ :ok
1370
+ }
1371
+
1372
+ # Generic fields_tbl
1373
+ n = N/2
1374
+ assert_equal "#{n}#{n}", %Q{
1375
+ 2.times.map{
1376
+ Ractor.new do
1377
+ #{n}.times do
1378
+ obj = +''
1379
+ obj.instance_variable_set("@a", 1)
1380
+ obj.instance_variable_set("@b", 1)
1381
+ obj.instance_variable_set("@c", 1)
1382
+ obj.instance_variable_defined?("@a")
1383
+ end
1384
+ end
1385
+ }.map{|r| r.value}.join
1386
+ }
1387
+
1388
+ # Now NoMethodError is copyable
1389
+ assert_equal "NoMethodError", %q{
1390
+ obj = "".freeze # NameError refers the receiver indirectly
1391
+ begin
1392
+ obj.bar
1393
+ rescue => err
1394
+ end
1395
+
1396
+ r = Ractor.new{ Ractor.receive }
1397
+ r << err
1398
+ r.value.class
1399
+ }
1400
+
1401
+ assert_equal "ok", %q{
1402
+ GC.disable
1403
+ Ractor.new {}
1404
+ raise "not ok" unless GC.disable
1405
+
1406
+ foo = []
1407
+ 10.times { foo << 1 }
1408
+
1409
+ GC.start
1410
+
1411
+ 'ok'
1412
+ }
1413
+
1414
+ # Can yield back values while GC is sweeping [Bug #18117]
1415
+ assert_equal "ok", %q{
1416
+ port = Ractor::Port.new
1417
+ workers = (0...8).map do
1418
+ Ractor.new port do |port|
1419
+ loop do
1420
+ 10_000.times.map { Object.new }
1421
+ port << Time.now
1422
+ Ractor.receive
1423
+ end
1424
+ end
1425
+ end
1426
+
1427
+ 100.times {
1428
+ workers.each do
1429
+ port.receive
1430
+ end
1431
+ workers.each do |w|
1432
+ w.send(nil)
1433
+ end
1434
+ }
1435
+ "ok"
1436
+ } # if !yjit_enabled? && ENV['GITHUB_WORKFLOW'] != 'ModGC' # flaky
1437
+
1438
+ # check method cache invalidation
1439
+ assert_equal "ok", %q{
1440
+ module M
1441
+ def foo
1442
+ @foo
1443
+ end
1444
+ end
1445
+
1446
+ class A
1447
+ include M
1448
+
1449
+ def initialize
1450
+ 100.times { |i| instance_variable_set(:"@var_#{i}", "bad: #{i}") }
1451
+ @foo = 2
1452
+ end
1453
+ end
1454
+
1455
+ class B
1456
+ include M
1457
+
1458
+ def initialize
1459
+ @foo = 1
1460
+ end
1461
+ end
1462
+
1463
+ r = Ractor.new do
1464
+ b = B.new
1465
+ 100_000.times do
1466
+ raise unless b.foo == 1
1467
+ end
1468
+ end
1469
+
1470
+ a = A.new
1471
+ 100_000.times do
1472
+ raise unless a.foo == 2
1473
+ end
1474
+
1475
+ r.join; "ok"
1476
+ }
1477
+
1478
+ # check method cache invalidation
1479
+ assert_equal 'true', %q{
1480
+ class C1; def self.foo = 1; end
1481
+ class C2; def self.foo = 2; end
1482
+ class C3; def self.foo = 3; end
1483
+ class C4; def self.foo = 5; end
1484
+ class C5; def self.foo = 7; end
1485
+ class C6; def self.foo = 11; end
1486
+ class C7; def self.foo = 13; end
1487
+ class C8; def self.foo = 17; end
1488
+
1489
+ LN = 10_000
1490
+ RN = 10
1491
+ CS = [C1, C2, C3, C4, C5, C6, C7, C8]
1492
+ rs = RN.times.map{|i|
1493
+ Ractor.new(CS.shuffle){|cs|
1494
+ LN.times.sum{
1495
+ cs.inject(1){|r, c| r * c.foo} # c.foo invalidates method cache entry
1496
+ }
1497
+ }
1498
+ }
1499
+
1500
+ n = CS.inject(1){|r, c| r * c.foo} * LN
1501
+ rs.map{|r| r.value} == Array.new(RN){n}
1502
+ }
1503
+
1504
+ # check method cache invalidation
1505
+ assert_equal 'true', %q{
1506
+ class Foo
1507
+ def hello = nil
1508
+ end
1509
+
1510
+ r1 = Ractor.new do
1511
+ 1000.times do
1512
+ class Foo
1513
+ def hello = nil
1514
+ end
1515
+ end
1516
+ end
1517
+
1518
+ r2 = Ractor.new do
1519
+ 1000.times do
1520
+ o = Foo.new
1521
+ o.hello
1522
+ end
1523
+ end
1524
+
1525
+ r1.value
1526
+ r2.value
1527
+
1528
+ true
1529
+ }
1530
+
1531
+ # check experimental warning
1532
+ assert_match /\Atest_ractor\.rb:1:\s+warning:\s+Ractor is experimental/, %q{
1533
+ Warning[:experimental] = $VERBOSE = true
1534
+ STDERR.reopen(STDOUT)
1535
+ eval("Ractor.new{}.value", nil, "test_ractor.rb", 1)
1536
+ }, frozen_string_literal: false
1537
+
1538
+ # check moved object
1539
+ assert_equal 'ok', %q{
1540
+ r = Ractor.new do
1541
+ Ractor.receive
1542
+ GC.start
1543
+ :ok
1544
+ end
1545
+
1546
+ obj = begin
1547
+ raise
1548
+ rescue => e
1549
+ e = Marshal.load(Marshal.dump(e))
1550
+ end
1551
+
1552
+ r.send obj, move: true
1553
+ r.value
1554
+ }
1555
+
1556
+ ## Ractor::Selector
1557
+
1558
+ # Selector#empty? returns true
1559
+ assert_equal 'true', %q{
1560
+ skip true unless defined? Ractor::Selector
1561
+
1562
+ s = Ractor::Selector.new
1563
+ s.empty?
1564
+ }
1565
+
1566
+ # Selector#empty? returns false if there is target ractors
1567
+ assert_equal 'false', %q{
1568
+ skip false unless defined? Ractor::Selector
1569
+
1570
+ s = Ractor::Selector.new
1571
+ s.add Ractor.new{}
1572
+ s.empty?
1573
+ }
1574
+
1575
+ # Selector#clear removes all ractors from the waiting list
1576
+ assert_equal 'true', %q{
1577
+ skip true unless defined? Ractor::Selector
1578
+
1579
+ s = Ractor::Selector.new
1580
+ s.add Ractor.new{10}
1581
+ s.add Ractor.new{20}
1582
+ s.clear
1583
+ s.empty?
1584
+ }
1585
+
1586
+ # Selector#wait can wait multiple ractors
1587
+ assert_equal '[10, 20, true]', %q{
1588
+ skip [10, 20, true] unless defined? Ractor::Selector
1589
+
1590
+ s = Ractor::Selector.new
1591
+ s.add Ractor.new{10}
1592
+ s.add Ractor.new{20}
1593
+ r, v = s.wait
1594
+ vs = []
1595
+ vs << v
1596
+ r, v = s.wait
1597
+ vs << v
1598
+ [*vs.sort, s.empty?]
1599
+ } if defined? Ractor::Selector
1600
+
1601
+ # Selector#wait can wait multiple ractors with receiving.
1602
+ assert_equal '30', %q{
1603
+ skip 30 unless defined? Ractor::Selector
1604
+
1605
+ RN = 30
1606
+ rs = RN.times.map{
1607
+ Ractor.new{ :v }
1608
+ }
1609
+ s = Ractor::Selector.new(*rs)
1610
+
1611
+ results = []
1612
+ until s.empty?
1613
+ results << s.wait
1614
+
1615
+ # Note that s.wait can raise an exception because other Ractors/Threads
1616
+ # can take from the same ractors in the waiting set.
1617
+ # In this case there is no other takers so `s.wait` doesn't raise an error.
1618
+ end
1619
+
1620
+ results.size
1621
+ } if defined? Ractor::Selector
1622
+
1623
+ # Selector#wait can support dynamic addition
1624
+ assert_equal '600', %q{
1625
+ skip 600 unless defined? Ractor::Selector
1626
+
1627
+ RN = 100
1628
+ s = Ractor::Selector.new
1629
+ port = Ractor::Port.new
1630
+ rs = RN.times.map{
1631
+ Ractor.new{
1632
+ Ractor.main << Ractor.new(port){|port| port << :v3; :v4 }
1633
+ Ractor.main << Ractor.new(port){|port| port << :v5; :v6 }
1634
+ Ractor.yield :v1
1635
+ :v2
1636
+ }
1637
+ }
1638
+
1639
+ rs.each{|r| s.add(r)}
1640
+ h = {v1: 0, v2: 0, v3: 0, v4: 0, v5: 0, v6: 0}
1641
+
1642
+ loop do
1643
+ case s.wait receive: true
1644
+ in :receive, r
1645
+ s.add r
1646
+ in r, v
1647
+ h[v] += 1
1648
+ break if h.all?{|k, v| v == RN}
1649
+ end
1650
+ end
1651
+
1652
+ h.sum{|k, v| v}
1653
+ } # unless yjit_enabled? # http://ci.rvm.jp/results/trunk-yjit@ruby-sp2-docker/4466770
1654
+
1655
+ # Selector should be GCed (free'ed) without trouble
1656
+ assert_equal 'ok', %q{
1657
+ skip :ok unless defined? Ractor::Selector
1658
+
1659
+ RN = 30
1660
+ rs = RN.times.map{
1661
+ Ractor.new{ :v }
1662
+ }
1663
+ s = Ractor::Selector.new(*rs)
1664
+ :ok
1665
+ }
1666
+
1667
+ end # if !ENV['GITHUB_WORKFLOW']
1668
+
1669
+ # Chilled strings are not shareable
1670
+ assert_equal 'false', %q{
1671
+ Ractor.shareable?("chilled")
1672
+ }
1673
+
1674
+ # Chilled strings can be made shareable
1675
+ assert_equal 'true', %q{
1676
+ shareable = Ractor.make_shareable("chilled")
1677
+ shareable == "chilled" && Ractor.shareable?(shareable)
1678
+ }
1679
+
1680
+ # require in Ractor
1681
+ assert_equal 'true', %q{
1682
+ Module.new do
1683
+ def require feature
1684
+ return Ractor._require(feature) unless Ractor.main?
1685
+ super
1686
+ end
1687
+ Object.prepend self
1688
+ # set_temporary_name 'Ractor#require'
1689
+ end
1690
+
1691
+ Ractor.new{
1692
+ begin
1693
+ require 'tempfile'
1694
+ Tempfile.new
1695
+ rescue SystemStackError
1696
+ # prism parser with -O0 build consumes a lot of machine stack
1697
+ Data.define(:fileno).new(1)
1698
+ end
1699
+ }.value.fileno > 0
1700
+ }
1701
+
1702
+ # require_relative in Ractor
1703
+ assert_equal 'true', %q{
1704
+ dummyfile = File.join(__dir__, "dummy#{rand}.rb")
1705
+ return true if File.exist?(dummyfile)
1706
+
1707
+ begin
1708
+ File.write dummyfile, ''
1709
+ rescue Exception
1710
+ # skip on any errors
1711
+ return true
1712
+ end
1713
+
1714
+ begin
1715
+ Ractor.new dummyfile do |f|
1716
+ require_relative File.basename(f)
1717
+ end.value
1718
+ ensure
1719
+ File.unlink dummyfile
1720
+ end
1721
+ }
1722
+
1723
+ # require_relative in Ractor
1724
+ assert_equal 'LoadError', %q{
1725
+ dummyfile = File.join(__dir__, "not_existed_dummy#{rand}.rb")
1726
+ return true if File.exist?(dummyfile)
1727
+
1728
+ Ractor.new dummyfile do |f|
1729
+ begin
1730
+ require_relative File.basename(f)
1731
+ rescue LoadError => e
1732
+ e.class
1733
+ end
1734
+ end.value
1735
+ }
1736
+
1737
+ # autolaod in Ractor
1738
+ assert_equal 'true', %q{
1739
+ autoload :Tempfile, 'tempfile'
1740
+
1741
+ r = Ractor.new do
1742
+ begin
1743
+ Tempfile.new
1744
+ rescue SystemStackError
1745
+ # prism parser with -O0 build consumes a lot of machine stack
1746
+ Data.define(:fileno).new(1)
1747
+ end
1748
+ end
1749
+ r.value.fileno > 0
1750
+ }
1751
+
1752
+ # failed in autolaod in Ractor
1753
+ assert_equal 'LoadError', %q{
1754
+ dummyfile = File.join(__dir__, "not_existed_dummy#{rand}.rb")
1755
+ autoload :Tempfile, dummyfile
1756
+
1757
+ r = Ractor.new do
1758
+ begin
1759
+ Tempfile.new
1760
+ rescue LoadError => e
1761
+ e.class
1762
+ end
1763
+ end
1764
+ r.value
1765
+ }
1766
+
1767
+ # bind_call in Ractor [Bug #20934]
1768
+ assert_equal 'ok', %q{
1769
+ 2.times.map do
1770
+ Ractor.new do
1771
+ 1000.times do
1772
+ Object.instance_method(:itself).bind_call(self)
1773
+ end
1774
+ end
1775
+ end.each(&:join)
1776
+ GC.start
1777
+ :ok.itself
1778
+ }
1779
+
1780
+ # moved objects being corrupted if embeded (String)
1781
+ assert_equal 'ok', %q{
1782
+ ractor = Ractor.new { Ractor.receive }
1783
+ obj = "foobarbazfoobarbazfoobarbazfoobarbaz"
1784
+ ractor.send(obj.dup, move: true)
1785
+ roundtripped_obj = ractor.value
1786
+ roundtripped_obj == obj ? :ok : roundtripped_obj
1787
+ }
1788
+
1789
+ # moved objects being corrupted if embeded (Array)
1790
+ assert_equal 'ok', %q{
1791
+ ractor = Ractor.new { Ractor.receive }
1792
+ obj = Array.new(10, 42)
1793
+ ractor.send(obj.dup, move: true)
1794
+ roundtripped_obj = ractor.value
1795
+ roundtripped_obj == obj ? :ok : roundtripped_obj
1796
+ }
1797
+
1798
+ # moved objects being corrupted if embeded (Hash)
1799
+ assert_equal 'ok', %q{
1800
+ ractor = Ractor.new { Ractor.receive }
1801
+ obj = { foo: 1, bar: 2 }
1802
+ ractor.send(obj.dup, move: true)
1803
+ roundtripped_obj = ractor.value
1804
+ roundtripped_obj == obj ? :ok : roundtripped_obj
1805
+ }
1806
+
1807
+ # moved objects being corrupted if embeded (MatchData)
1808
+ assert_equal 'ok', %q{
1809
+ ractor = Ractor.new { Ractor.receive }
1810
+ obj = "foo".match(/o/)
1811
+ ractor.send(obj.dup, move: true)
1812
+ roundtripped_obj = ractor.value
1813
+ roundtripped_obj == obj ? :ok : roundtripped_obj
1814
+ }
1815
+
1816
+ # moved objects being corrupted if embeded (Struct)
1817
+ assert_equal 'ok', %q{
1818
+ ractor = Ractor.new { Ractor.receive }
1819
+ obj = Struct.new(:a, :b, :c, :d, :e, :f).new(1, 2, 3, 4, 5, 6)
1820
+ ractor.send(obj.dup, move: true)
1821
+ roundtripped_obj = ractor.value
1822
+ roundtripped_obj == obj ? :ok : roundtripped_obj
1823
+ }
1824
+
1825
+ # moved objects being corrupted if embeded (Object)
1826
+ assert_equal 'ok', %q{
1827
+ ractor = Ractor.new { Ractor.receive }
1828
+ class SomeObject
1829
+ attr_reader :a, :b, :c, :d, :e, :f
1830
+ def initialize
1831
+ @a = @b = @c = @d = @e = @f = 1
1832
+ end
1833
+
1834
+ def ==(o)
1835
+ @a == o.a &&
1836
+ @b == o.b &&
1837
+ @c == o.c &&
1838
+ @d == o.d &&
1839
+ @e == o.e &&
1840
+ @f == o.f
1841
+ end
1842
+ end
1843
+
1844
+ SomeObject.new # initial non-embeded
1845
+
1846
+ obj = SomeObject.new
1847
+ ractor.send(obj.dup, move: true)
1848
+ roundtripped_obj = ractor.value
1849
+ roundtripped_obj == obj ? :ok : roundtripped_obj
1850
+ }
1851
+
1852
+ # moved arrays can't be used
1853
+ assert_equal 'ok', %q{
1854
+ ractor = Ractor.new { Ractor.receive }
1855
+ obj = [1]
1856
+ ractor.send(obj, move: true)
1857
+ begin
1858
+ [].concat(obj)
1859
+ rescue TypeError
1860
+ :ok
1861
+ else
1862
+ :fail
1863
+ end
1864
+ }
1865
+
1866
+ # moved strings can't be used
1867
+ assert_equal 'ok', %q{
1868
+ ractor = Ractor.new { Ractor.receive }
1869
+ obj = "hello"
1870
+ ractor.send(obj, move: true)
1871
+ begin
1872
+ "".replace(obj)
1873
+ rescue TypeError
1874
+ :ok
1875
+ else
1876
+ :fail
1877
+ end
1878
+ }
1879
+
1880
+ # moved hashes can't be used
1881
+ assert_equal 'ok', %q{
1882
+ ractor = Ractor.new { Ractor.receive }
1883
+ obj = { a: 1 }
1884
+ ractor.send(obj, move: true)
1885
+ begin
1886
+ {}.merge(obj)
1887
+ rescue TypeError
1888
+ :ok
1889
+ else
1890
+ :fail
1891
+ end
1892
+ }
1893
+
1894
+ # move objects inside frozen containers
1895
+ assert_equal 'ok', %q{
1896
+ ractor = Ractor.new { Ractor.receive }
1897
+ obj = Array.new(10, 42)
1898
+ original = obj.dup
1899
+ ractor.send([obj].freeze, move: true)
1900
+ roundtripped_obj = ractor.value[0]
1901
+ roundtripped_obj == original ? :ok : roundtripped_obj
1902
+ }
1903
+
1904
+ # move object with generic ivar
1905
+ assert_equal 'ok', %q{
1906
+ ractor = Ractor.new { Ractor.receive }
1907
+ obj = Array.new(10, 42)
1908
+ obj.instance_variable_set(:@array, [1])
1909
+
1910
+ ractor.send(obj, move: true)
1911
+ roundtripped_obj = ractor.value
1912
+ roundtripped_obj.instance_variable_get(:@array) == [1] ? :ok : roundtripped_obj
1913
+ }
1914
+
1915
+ # move object with many generic ivars
1916
+ assert_equal 'ok', %q{
1917
+ ractor = Ractor.new { Ractor.receive }
1918
+ obj = Array.new(10, 42)
1919
+ 0.upto(300) do |i|
1920
+ obj.instance_variable_set(:"@array#{i}", [i])
1921
+ end
1922
+
1923
+ ractor.send(obj, move: true)
1924
+ roundtripped_obj = ractor.value
1925
+ roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj
1926
+ }
1927
+
1928
+ # move object with complex generic ivars
1929
+ assert_equal 'ok', %q{
1930
+ # Make Array too_complex
1931
+ 30.times { |i| [].instance_variable_set(:"@complex#{i}", 1) }
1932
+
1933
+ ractor = Ractor.new { Ractor.receive }
1934
+ obj = Array.new(10, 42)
1935
+ obj.instance_variable_set(:@array1, [1])
1936
+
1937
+ ractor.send(obj, move: true)
1938
+ roundtripped_obj = ractor.value
1939
+ roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj
1940
+ }
1941
+
1942
+ # copy object with complex generic ivars
1943
+ assert_equal 'ok', %q{
1944
+ # Make Array too_complex
1945
+ 30.times { |i| [].instance_variable_set(:"@complex#{i}", 1) }
1946
+
1947
+ ractor = Ractor.new { Ractor.receive }
1948
+ obj = Array.new(10, 42)
1949
+ obj.instance_variable_set(:@array1, [1])
1950
+
1951
+ ractor.send(obj)
1952
+ roundtripped_obj = ractor.value
1953
+ roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj
1954
+ }
1955
+
1956
+ # copy object with many generic ivars
1957
+ assert_equal 'ok', %q{
1958
+ ractor = Ractor.new { Ractor.receive }
1959
+ obj = Array.new(10, 42)
1960
+ 0.upto(300) do |i|
1961
+ obj.instance_variable_set(:"@array#{i}", [i])
1962
+ end
1963
+
1964
+ ractor.send(obj)
1965
+ roundtripped_obj = ractor.value
1966
+ roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj
1967
+ }
1968
+
1969
+ # moved composite types move their non-shareable parts properly
1970
+ assert_equal 'ok', %q{
1971
+ k, v = String.new("key"), String.new("value")
1972
+ h = { k => v }
1973
+ h.instance_variable_set("@b", String.new("b"))
1974
+ a = [k,v]
1975
+ o_singleton = Object.new
1976
+ def o_singleton.a
1977
+ @a
1978
+ end
1979
+ o_singleton.instance_variable_set("@a", String.new("a"))
1980
+ class MyObject
1981
+ attr_reader :a
1982
+ def initialize(a)
1983
+ @a = a
1984
+ end
1985
+ end
1986
+ struct_class = Struct.new(:a)
1987
+ struct = struct_class.new(String.new('a'))
1988
+ o = MyObject.new(String.new('a'))
1989
+ port = Ractor::Port.new
1990
+
1991
+ r = Ractor.new port do |port|
1992
+ loop do
1993
+ obj = Ractor.receive
1994
+ val = case obj
1995
+ when Hash
1996
+ obj['key'] == 'value' && obj.instance_variable_get("@b") == 'b'
1997
+ when Array
1998
+ obj[0] == 'key'
1999
+ when Struct
2000
+ obj.a == 'a'
2001
+ when Object
2002
+ obj.a == 'a'
2003
+ end
2004
+ port << val
2005
+ end
2006
+ end
2007
+
2008
+ objs = [h, a, o_singleton, o, struct]
2009
+ objs.each_with_index do |obj, i|
2010
+ klass = obj.class
2011
+ parts_moved = {}
2012
+ case obj
2013
+ when Hash
2014
+ parts_moved[klass] = [obj['key'], obj.instance_variable_get("@b")]
2015
+ when Array
2016
+ parts_moved[klass] = obj.dup # the contents
2017
+ when Struct, Object
2018
+ parts_moved[klass] = [obj.a]
2019
+ end
2020
+ r.send(obj, move: true)
2021
+ val = port.receive
2022
+ if val != true
2023
+ raise "bad val in ractor for obj at i:#{i}"
2024
+ end
2025
+ begin
2026
+ p obj
2027
+ rescue
2028
+ else
2029
+ raise "should be moved"
2030
+ end
2031
+ parts_moved.each do |klass, parts|
2032
+ parts.each_with_index do |part, j|
2033
+ case part
2034
+ when Ractor::MovedObject
2035
+ else
2036
+ raise "part for class #{klass} at i:#{j} should be moved"
2037
+ end
2038
+ end
2039
+ end
2040
+ end
2041
+ 'ok'
2042
+ }
2043
+
2044
+ # fork after creating Ractor
2045
+ assert_equal 'ok', %q{
2046
+ begin
2047
+ Ractor.new { Ractor.receive }
2048
+ _, status = Process.waitpid2 fork { }
2049
+ status.success? ? "ok" : status
2050
+ rescue NotImplementedError
2051
+ :ok
2052
+ end
2053
+ }
2054
+
2055
+ # Ractors should be terminated after fork
2056
+ assert_equal 'ok', %q{
2057
+ begin
2058
+ r = Ractor.new { Ractor.receive }
2059
+ _, status = Process.waitpid2 fork {
2060
+ begin
2061
+ raise if r.value != nil
2062
+ end
2063
+ }
2064
+ r.send(123)
2065
+ raise unless r.value == 123
2066
+ status.success? ? "ok" : status
2067
+ rescue NotImplementedError
2068
+ :ok
2069
+ end
2070
+ }
2071
+
2072
+ # Ractors should be terminated after fork
2073
+ assert_equal 'ok', %q{
2074
+ begin
2075
+ r = Ractor.new { Ractor.receive }
2076
+ _, status = Process.waitpid2 fork {
2077
+ begin
2078
+ r.send(123)
2079
+ rescue Ractor::ClosedError
2080
+ end
2081
+ }
2082
+ r.send(123)
2083
+ raise unless r.value == 123
2084
+ status.success? ? "ok" : status
2085
+ rescue NotImplementedError
2086
+ :ok
2087
+ end
2088
+ }
2089
+
2090
+ # Creating classes inside of Ractors
2091
+ # [Bug #18119]
2092
+ assert_equal 'ok', %q{
2093
+ port = Ractor::Port.new
2094
+ workers = (0...8).map do
2095
+ Ractor.new port do |port|
2096
+ loop do
2097
+ 100.times.map { Class.new }
2098
+ port << nil
2099
+ end
2100
+ end
2101
+ end
2102
+
2103
+ 100.times { port.receive }
2104
+
2105
+ 'ok'
2106
+ }
2107
+
2108
+ # Using Symbol#to_proc inside ractors
2109
+ # [Bug #21354]
2110
+ assert_equal 'ok', %q{
2111
+ :inspect.to_proc
2112
+ Ractor.new do
2113
+ # It should not use this cached proc, it should create a new one. If it used
2114
+ # the cached proc, we would get a ractor_confirm_belonging error here.
2115
+ :inspect.to_proc
2116
+ end.join
2117
+ 'ok'
2118
+ }
2119
+
2120
+ # take vm lock when deleting generic ivars from the global table
2121
+ assert_equal 'ok', %q{
2122
+ Ractor.new do
2123
+ a = [1, 2, 3]
2124
+ a.object_id
2125
+ a.dup # this deletes generic ivar on dupped object
2126
+ 'ok'
2127
+ end.value
2128
+ }
2129
+
2130
+ ## Ractor#monitor
2131
+
2132
+ # monitor port returns `:exited` when the monitering Ractor terminated.
2133
+ assert_equal 'true', %q{
2134
+ r = Ractor.new do
2135
+ Ractor.main << :ok1
2136
+ :ok2
2137
+ end
2138
+
2139
+ r.monitor port = Ractor::Port.new
2140
+ Ractor.receive # :ok1
2141
+ port.receive == :exited
2142
+ }
2143
+
2144
+ # monitor port returns `:exited` even if the monitoring Ractor was terminated.
2145
+ assert_equal 'true', %q{
2146
+ r = Ractor.new do
2147
+ :ok
2148
+ end
2149
+
2150
+ r.join # wait for r's terminateion
2151
+
2152
+ r.monitor port = Ractor::Port.new
2153
+ port.receive == :exited
2154
+ }
2155
+
2156
+ # monitor returns false if the monitoring Ractor was terminated.
2157
+ assert_equal 'false', %q{
2158
+ r = Ractor.new do
2159
+ :ok
2160
+ end
2161
+
2162
+ r.join # wait for r's terminateion
2163
+
2164
+ r.monitor Ractor::Port.new
2165
+ }
2166
+
2167
+ # monitor port returns `:aborted` when the monitering Ractor is aborted.
2168
+ assert_equal 'true', %q{
2169
+ r = Ractor.new do
2170
+ Ractor.main << :ok1
2171
+ raise 'ok'
2172
+ end
2173
+
2174
+ r.monitor port = Ractor::Port.new
2175
+ Ractor.receive # :ok1
2176
+ port.receive == :aborted
2177
+ }
2178
+
2179
+ # monitor port returns `:aborted` even if the monitoring Ractor was aborted.
2180
+ assert_equal 'true', %q{
2181
+ r = Ractor.new do
2182
+ raise 'ok'
2183
+ end
2184
+
2185
+ begin
2186
+ r.join # wait for r's terminateion
2187
+ rescue Ractor::RemoteError
2188
+ # ignore
2189
+ end
2190
+
2191
+ r.monitor port = Ractor::Port.new
2192
+ port.receive == :aborted
2193
+ }
2194
+
2195
+ ## Ractor#join
2196
+
2197
+ # Ractor#join returns self when the Ractor is terminated.
2198
+ assert_equal 'true', %q{
2199
+ r = Ractor.new do
2200
+ Ractor.receive
2201
+ end
2202
+
2203
+ r << :ok
2204
+ r.join
2205
+ r.inspect in /terminated/
2206
+ } if false # TODO
2207
+
2208
+ # Ractor#join raises RemoteError when the remote Ractor aborted with an exception
2209
+ assert_equal 'err', %q{
2210
+ r = Ractor.new do
2211
+ raise 'err'
2212
+ end
2213
+
2214
+ begin
2215
+ r.join
2216
+ rescue Ractor::RemoteError => e
2217
+ e.cause.message
2218
+ end
2219
+ }
2220
+
2221
+ ## Ractor#value
2222
+
2223
+ # Ractor#value returns the last expression even if it is unshareable
2224
+ assert_equal 'true', %q{
2225
+ r = Ractor.new do
2226
+ obj = [1, 2]
2227
+ obj << obj.object_id
2228
+ end
2229
+
2230
+ ret = r.value
2231
+ ret == [1, 2, ret.object_id]
2232
+ }
2233
+
2234
+ # Only one Ractor can call Ractor#value
2235
+ assert_equal '[["Only the successor ractor can take a value", 9], ["ok", 2]]', %q{
2236
+ r = Ractor.new do
2237
+ 'ok'
2238
+ end
2239
+
2240
+ RN = 10
2241
+
2242
+ rs = RN.times.map do
2243
+ Ractor.new r do |r|
2244
+ begin
2245
+ Ractor.main << r.value
2246
+ Ractor.main << r.value # this ractor can get same result
2247
+ rescue Ractor::Error => e
2248
+ Ractor.main << e.message
2249
+ end
2250
+ end
2251
+ end
2252
+
2253
+ (RN+1).times.map{
2254
+ Ractor.receive
2255
+ }.tally.sort
2256
+ }
2257
+
2258
+ # Cause lots of inline CC misses.
2259
+ assert_equal 'ok', <<~'RUBY'
2260
+ class A; def test; 1 + 1; end; end
2261
+ class B; def test; 1 + 1; end; end
2262
+ class C; def test; 1 + 1; end; end
2263
+ class D; def test; 1 + 1; end; end
2264
+ class E; def test; 1 + 1; end; end
2265
+ class F; def test; 1 + 1; end; end
2266
+ class G; def test; 1 + 1; end; end
2267
+
2268
+ objs = [A.new, B.new, C.new, D.new, E.new, F.new, G.new].freeze
2269
+
2270
+ def call_test(obj)
2271
+ obj.test
2272
+ end
2273
+
2274
+ ractors = 7.times.map do
2275
+ Ractor.new(objs) do |objs|
2276
+ objs = objs.shuffle
2277
+ 100_000.times do
2278
+ objs.each do |o|
2279
+ call_test(o)
2280
+ end
2281
+ end
2282
+ end
2283
+ end
2284
+ ractors.each(&:join)
2285
+ :ok
2286
+ RUBY
2287
+
2288
+ # This test checks that we do not trigger a GC when we have malloc with Ractor
2289
+ # locks. We cannot trigger a GC with Ractor locks because GC requires VM lock
2290
+ # and Ractor barrier. If another Ractor is waiting on this Ractor lock, then it
2291
+ # will deadlock because the other Ractor will never join the barrier.
2292
+ #
2293
+ # Creating Ractor::Port requires locking the Ractor and inserting into an
2294
+ # st_table, which can call malloc.
2295
+ assert_equal 'ok', <<~'RUBY'
2296
+ r = Ractor.new do
2297
+ loop do
2298
+ Ractor::Port.new
2299
+ end
2300
+ end
2301
+
2302
+ 10.times do
2303
+ 10_000.times do
2304
+ r.send(nil)
2305
+ end
2306
+ sleep(0.01)
2307
+ end
2308
+ :ok
2309
+ RUBY