ractor-shim 0.0.1 → 0.1.1

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