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.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/Rakefile +5 -1
- data/lib/ractor/shim.rb +383 -1
- data/test/run_tests.rb +235 -0
- data/test/test_ractor.rb +2309 -0
- metadata +9 -4
- data/lib/ractor/shim/version.rb +0 -7
data/test/test_ractor.rb
ADDED
|
@@ -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
|