1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
# frozen_string_literal: false
require 'test/unit'
class TestRactor < Test::Unit::TestCase
def test_shareability_of_iseq_proc
assert_raise Ractor::IsolationError do
foo = []
Ractor.shareable_proc{ foo }
end
end
def test_shareability_of_method_proc
# TODO: fix with Ractor.shareable_proc/lambda
=begin
str = +""
x = str.instance_exec { proc { to_s } }
assert_unshareable(x, /Proc\'s self is not shareable/)
x = str.instance_exec { method(:to_s) }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:to_s).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:itself).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
str.freeze
x = str.instance_exec { proc { to_s } }
assert_make_shareable(x)
x = str.instance_exec { method(:to_s) }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:to_s).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error)
x = str.instance_exec { method(:itself).to_proc }
assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error)
=end
end
def test_shareability_error_uses_inspect
x = (+"").instance_exec { method(:to_s) }
def x.to_s
raise "this should not be called"
end
assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()> because it refers unshareable objects", exception: Ractor::Error)
end
def test_default_thread_group
assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}")
begin;
Warning[:experimental] = false
main_ractor_id = Thread.current.group.object_id
ractor_id = Ractor.new { Thread.current.group.object_id }.value
refute_equal main_ractor_id, ractor_id
end;
end
def test_class_instance_variables
assert_ractor(<<~'RUBY')
# Once we're in multi-ractor mode, the codepaths
# for class instance variables are a bit different.
Ractor.new {}.value
class TestClass
@a = 1
@b = 2
@c = 3
@d = 4
end
assert_equal 4, TestClass.remove_instance_variable(:@d)
assert_nil TestClass.instance_variable_get(:@d)
assert_equal 4, TestClass.instance_variable_set(:@d, 4)
assert_equal 4, TestClass.instance_variable_get(:@d)
RUBY
end
def test_struct_instance_variables
assert_ractor(<<~'RUBY')
StructIvar = Struct.new(:member) do
def initialize(*)
super
@ivar = "ivar"
end
attr_reader :ivar
end
obj = StructIvar.new("member")
obj_copy = Ractor.new { Ractor.receive }.send(obj).value
assert_equal obj.ivar, obj_copy.ivar
refute_same obj.ivar, obj_copy.ivar
assert_equal obj.member, obj_copy.member
refute_same obj.member, obj_copy.member
RUBY
end
def test_move_nested_hash_during_gc_with_yjit
assert_ractor(<<~'RUBY', args: [{ "RUBY_YJIT_ENABLE" => "1" }])
GC.stress = true
hash = { foo: { bar: "hello" }, baz: { qux: "there" } }
result = Ractor.new { Ractor.receive }.send(hash, move: true).value
assert_equal "hello", result[:foo][:bar]
assert_equal "there", result[:baz][:qux]
RUBY
end
def test_fork_raise_isolation_error
assert_ractor(<<~'RUBY')
ractor = Ractor.new do
Process.fork
rescue Ractor::IsolationError => e
e
end
assert_equal Ractor::IsolationError, ractor.value.class
RUBY
end if Process.respond_to?(:fork)
def test_require_raises_and_no_ractor_belonging_issue
assert_ractor(<<~'RUBY')
require "tempfile"
f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
f.write("raise 'uh oh'")
f.flush
err_msg = Ractor.new(f.path) do |path|
begin
require path
rescue RuntimeError => e
e.message # had confirm belonging issue here
else
nil
end
end.value
assert_equal "uh oh", err_msg
RUBY
end
def test_require_non_string
assert_ractor(<<~'RUBY')
require "tempfile"
require "pathname"
f = Tempfile.new(["file_to_require_from_ractor", ".rb"])
f.write("")
f.flush
result = Ractor.new(f.path) do |path|
require Pathname.new(path)
"success"
end.value
assert_equal "success", result
RUBY
end
# [Bug #21398]
def test_port_receive_dnt_with_port_send
omit 'unstable on windows and macos-14' if RUBY_PLATFORM =~ /mswin|mingw|darwin/
assert_ractor(<<~'RUBY', timeout: 90)
THREADS = 10
JOBS_PER_THREAD = 50
ARRAY_SIZE = 20_000
def ractor_job(job_count, array_size)
port = Ractor::Port.new
workers = (1..4).map do |i|
Ractor.new(port) do |job_port|
while job = Ractor.receive
result = job.map { |x| x * 2 }.sum
job_port.send result
end
end
end
jobs = Array.new(job_count) { Array.new(array_size) { rand(1000) } }
jobs.each_with_index do |job, i|
w_idx = i % 4
workers[w_idx].send(job)
end
results = []
jobs.size.times do
result = port.receive # dnt receive
results << result
end
results
end
threads = []
# creates 40 ractors (THREADSx4)
THREADS.times do
threads << Thread.new do
ractor_job(JOBS_PER_THREAD, ARRAY_SIZE)
end
end
threads.each(&:join)
RUBY
end
# [Bug #20146]
def test_max_cpu_1
assert_ractor(<<~'RUBY', args: [{ "RUBY_MAX_CPU" => "1" }])
assert_equal :ok, Ractor.new { :ok }.value
RUBY
end
def test_symbol_proc_is_shareable
pr = :symbol.to_proc
assert_make_shareable(pr)
end
# [Bug #21775]
def test_ifunc_proc_not_shareable
h = Hash.new { self }
pr = h.to_proc
assert_unshareable(pr, /not supported yet/, exception: RuntimeError)
end
def assert_make_shareable(obj)
refute Ractor.shareable?(obj), "object was already shareable"
Ractor.make_shareable(obj)
assert Ractor.shareable?(obj), "object didn't become shareable"
end
def assert_unshareable(obj, msg=nil, exception: Ractor::IsolationError)
refute Ractor.shareable?(obj), "object is already shareable"
assert_raise_with_message(exception, msg) do
Ractor.make_shareable(obj)
end
refute Ractor.shareable?(obj), "despite raising, object became shareable"
end
end
|