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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8bb91e5a7525967249c155d6193ebacf3b261d6db8c93c06e904aabc11810c79
|
|
4
|
+
data.tar.gz: 81882745a9a7393a06a1916c19b5324272ff5001a9883c61ea4d7d2fadb032c2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 80866fae0411eb23c5e1900430763c7d1ce552f0704c911e9c65a7a7b518959f3ee85d2ce0836f4ea33291c37e0061ffa8266086ad2f78d1fc5509991729690a
|
|
7
|
+
data.tar.gz: 42fae31b67236b12e74f84b4d4942e34fe7a4f73fa9280fa971733e30c16edef9a60a8a29312aca99fcac9374050a328d86c0148121c1fffe0b9a5dfd0249477
|
data/README.md
CHANGED
|
@@ -12,7 +12,7 @@ require 'ractor/shim'
|
|
|
12
12
|
|
|
13
13
|
## Development
|
|
14
14
|
|
|
15
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `
|
|
15
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `ractor-shim.gemspec`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
16
16
|
|
|
17
17
|
## Contributing
|
|
18
18
|
|
data/Rakefile
CHANGED
data/lib/ractor/shim.rb
CHANGED
|
@@ -1,3 +1,385 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
builtin_ractor = !!defined?(Ractor)
|
|
4
|
+
class Ractor
|
|
5
|
+
end
|
|
6
|
+
Ractor.define_singleton_method(:builtin?) { builtin_ractor }
|
|
7
|
+
Ractor.define_singleton_method(:shim?) { !builtin_ractor }
|
|
8
|
+
|
|
9
|
+
if Ractor.shim?
|
|
10
|
+
class Ractor
|
|
11
|
+
class Error < RuntimeError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class RemoteError < Error
|
|
15
|
+
attr_reader :ractor
|
|
16
|
+
def initialize(ractor)
|
|
17
|
+
@ractor = ractor
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class ClosedError < StopIteration
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
@count = 1
|
|
25
|
+
COUNT_MUTEX = Mutex.new
|
|
26
|
+
CHANGE_COUNT = -> delta {
|
|
27
|
+
COUNT_MUTEX.synchronize { @count += delta }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@id = 0
|
|
31
|
+
ID_MUTEX = Mutex.new
|
|
32
|
+
GET_ID = -> {
|
|
33
|
+
ID_MUTEX.synchronize { @id += 1 }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
SELECT_MUTEX = Mutex.new
|
|
37
|
+
SELECT_CV = ConditionVariable.new
|
|
38
|
+
|
|
39
|
+
def self.main
|
|
40
|
+
MAIN_RACTOR
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.main?
|
|
44
|
+
current == main
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.current
|
|
48
|
+
Thread.current.thread_variable_get(:current_ractor) or raise "Could not find current Ractor"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def self.count
|
|
52
|
+
COUNT_MUTEX.synchronize { @count }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def self.receive
|
|
56
|
+
Ractor.current.__send__(:receive)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# def self.select_simple_spinning(*ractors)
|
|
60
|
+
# raise ArgumentError, "specify at least one ractor or `yield_value`" if ractors.empty?
|
|
61
|
+
# while true
|
|
62
|
+
# ractors.each do |ractor_or_port|
|
|
63
|
+
# if Ractor === ractor_or_port
|
|
64
|
+
# queue = ractor_or_port.out_queue
|
|
65
|
+
# elsif Ractor::Port === ractor_or_port
|
|
66
|
+
# queue = ractor_or_port.queue
|
|
67
|
+
# else
|
|
68
|
+
# raise ArgumentError, "Unexpected argument for Ractor.select: #{ractor_or_port}"
|
|
69
|
+
# end
|
|
70
|
+
#
|
|
71
|
+
# begin
|
|
72
|
+
# value = queue.pop(true)
|
|
73
|
+
# return [ractor_or_port, value]
|
|
74
|
+
# rescue ThreadError
|
|
75
|
+
# Thread.pass
|
|
76
|
+
# end
|
|
77
|
+
# end
|
|
78
|
+
# end
|
|
79
|
+
# end
|
|
80
|
+
|
|
81
|
+
def self.select(*ractors)
|
|
82
|
+
raise ArgumentError, "specify at least one ractor or `yield_value`" if ractors.empty?
|
|
83
|
+
|
|
84
|
+
SELECT_MUTEX.synchronize do
|
|
85
|
+
while true
|
|
86
|
+
ractors.each do |ractor_or_port|
|
|
87
|
+
if Ractor === ractor_or_port
|
|
88
|
+
queue = ractor_or_port.out_queue
|
|
89
|
+
elsif Ractor::Port === ractor_or_port
|
|
90
|
+
queue = ractor_or_port.queue
|
|
91
|
+
else
|
|
92
|
+
raise ArgumentError, "Unexpected argument for Ractor.select: #{ractor_or_port}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
begin
|
|
96
|
+
value = queue.pop(true)
|
|
97
|
+
return [ractor_or_port, value]
|
|
98
|
+
rescue ThreadError
|
|
99
|
+
# keep looping
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Wait until an item is added to a relevant Queue
|
|
104
|
+
SELECT_CV.wait(SELECT_MUTEX)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.make_shareable(object, copy: false)
|
|
110
|
+
# no copy, just return the object
|
|
111
|
+
object
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def self.shareable?(object)
|
|
115
|
+
true
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def self.store_if_absent(var, &block)
|
|
119
|
+
Ractor.current.__send__(:store_if_absent, var, &block)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self._require(feature)
|
|
123
|
+
Kernel.require(feature)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
attr_reader :name
|
|
127
|
+
|
|
128
|
+
attr_reader :in_queue, :out_queue
|
|
129
|
+
|
|
130
|
+
def initialize(*args, name: nil, &block)
|
|
131
|
+
raise ArgumentError, "must be called with a block" unless block
|
|
132
|
+
initialize_common(name, block)
|
|
133
|
+
|
|
134
|
+
@thread = Thread.new {
|
|
135
|
+
Thread.current.thread_variable_set(:current_ractor, self)
|
|
136
|
+
CHANGE_COUNT.call(1)
|
|
137
|
+
begin
|
|
138
|
+
result = self.instance_exec(*args, &block)
|
|
139
|
+
SELECT_MUTEX.synchronize {
|
|
140
|
+
unless @out_queue.closed?
|
|
141
|
+
@out_queue << result
|
|
142
|
+
SELECT_CV.broadcast
|
|
143
|
+
end
|
|
144
|
+
}
|
|
145
|
+
result
|
|
146
|
+
rescue Exception => e
|
|
147
|
+
@exception = e
|
|
148
|
+
nil
|
|
149
|
+
ensure
|
|
150
|
+
@termination_mutex.synchronize {
|
|
151
|
+
CHANGE_COUNT.call(-1)
|
|
152
|
+
@status = :terminated
|
|
153
|
+
|
|
154
|
+
monitor_message = @exception ? :aborted : :exited
|
|
155
|
+
@monitors.each { |monitor|
|
|
156
|
+
monitor << monitor_message
|
|
157
|
+
}
|
|
158
|
+
@monitors.clear
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
private def initialize_main
|
|
165
|
+
initialize_common(nil, nil)
|
|
166
|
+
@thread = nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
private def initialize_common(name, block)
|
|
170
|
+
@name = name.nil? ? nil : (String.try_convert(name) or raise TypeError)
|
|
171
|
+
@id = GET_ID.call
|
|
172
|
+
@status = :running
|
|
173
|
+
@from = block ? block.source_location.join(":") : nil
|
|
174
|
+
@in_queue = Queue.new
|
|
175
|
+
@out_queue = Queue.new
|
|
176
|
+
@storage = {}
|
|
177
|
+
@exception = nil
|
|
178
|
+
@monitors = []
|
|
179
|
+
@termination_mutex = Mutex.new
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def [](var)
|
|
183
|
+
raise "Cannot get ractor local storage for non-current ractor" unless Ractor.current == self
|
|
184
|
+
@storage[var]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def []=(var, value)
|
|
188
|
+
raise "Cannot set ractor local storage for non-current ractor" unless Ractor.current == self
|
|
189
|
+
@storage[var] = value
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
private def store_if_absent(var, &block)
|
|
193
|
+
if value = @storage[var]
|
|
194
|
+
value
|
|
195
|
+
else
|
|
196
|
+
value = block.call
|
|
197
|
+
@storage[var] = value
|
|
198
|
+
value
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def send(message, move: false)
|
|
203
|
+
raise Ractor::ClosedError, "The port was already closed" if @status == :terminated || @in_queue.closed?
|
|
204
|
+
@in_queue << message
|
|
205
|
+
self
|
|
206
|
+
end
|
|
207
|
+
alias_method :<<, :send
|
|
208
|
+
|
|
209
|
+
private def receive
|
|
210
|
+
@in_queue.pop
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# def take
|
|
214
|
+
# @out_queue.pop
|
|
215
|
+
# end
|
|
216
|
+
|
|
217
|
+
def close_incoming
|
|
218
|
+
@in_queue.close
|
|
219
|
+
self
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def close_outgoing
|
|
223
|
+
SELECT_MUTEX.synchronize {
|
|
224
|
+
@out_queue.close
|
|
225
|
+
SELECT_CV.broadcast
|
|
226
|
+
}
|
|
227
|
+
self
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def monitor(port)
|
|
231
|
+
@termination_mutex.synchronize {
|
|
232
|
+
if @status == :terminated
|
|
233
|
+
port << (@exception ? :aborted : :exited)
|
|
234
|
+
false
|
|
235
|
+
else
|
|
236
|
+
@monitors << port
|
|
237
|
+
end
|
|
238
|
+
}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def unmonitor(port)
|
|
242
|
+
@termination_mutex.synchronize {
|
|
243
|
+
@monitors.delete port
|
|
244
|
+
}
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def join
|
|
248
|
+
value
|
|
249
|
+
self
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def value
|
|
253
|
+
@thread.join
|
|
254
|
+
|
|
255
|
+
if exc = @exception
|
|
256
|
+
remote_error = RemoteError.new(self)
|
|
257
|
+
raise remote_error, cause: exc
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
@thread.value
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def inspect
|
|
264
|
+
["#<Ractor:##{@id}", @name, @from, "#{@status}>"].compact.join(' ')
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
MAIN_RACTOR = Ractor.allocate
|
|
268
|
+
MAIN_RACTOR.__send__(:initialize_main)
|
|
269
|
+
Thread.main.thread_variable_set(:current_ractor, MAIN_RACTOR)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
if Ractor.builtin?
|
|
274
|
+
class Ractor
|
|
275
|
+
unless method_defined?(:join)
|
|
276
|
+
alias_method :join, :take
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
unless method_defined?(:value)
|
|
280
|
+
alias_method :value, :take
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
unless respond_to?(:main?)
|
|
284
|
+
def self.main?
|
|
285
|
+
self == Ractor.main
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# common
|
|
292
|
+
class Ractor
|
|
293
|
+
unless method_defined?(:close)
|
|
294
|
+
def close
|
|
295
|
+
close_incoming
|
|
296
|
+
close_outgoing
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Ractor.{shareable_proc,shareable_lambda}
|
|
302
|
+
|
|
303
|
+
class << Ractor
|
|
304
|
+
if Ractor.builtin?
|
|
305
|
+
unless method_defined?(:shareable_proc)
|
|
306
|
+
def shareable_proc(&b)
|
|
307
|
+
Ractor.make_shareable(b)
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
unless method_defined?(:shareable_lambda)
|
|
312
|
+
def shareable_lambda(&b)
|
|
313
|
+
Ractor.make_shareable(b)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
else
|
|
317
|
+
unless method_defined?(:shareable_proc)
|
|
318
|
+
alias_method :shareable_proc, :proc
|
|
319
|
+
public :shareable_proc
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
unless method_defined?(:shareable_lambda)
|
|
323
|
+
alias_method :shareable_lambda, :lambda
|
|
324
|
+
public :shareable_lambda
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
# Ractor::Port
|
|
330
|
+
|
|
331
|
+
if Ractor.builtin?
|
|
332
|
+
class Ractor::Port
|
|
333
|
+
QUIT = Object.new.freeze
|
|
334
|
+
|
|
335
|
+
def initialize
|
|
336
|
+
@pipe = Ractor.new do
|
|
337
|
+
while true
|
|
338
|
+
msg = Ractor.receive
|
|
339
|
+
break if QUIT.equal?(msg)
|
|
340
|
+
Ractor.yield msg
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def <<(message)
|
|
346
|
+
@pipe.send(message)
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
def receive
|
|
350
|
+
@pipe.take
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
def close
|
|
354
|
+
@pipe.send(QUIT)
|
|
355
|
+
end
|
|
356
|
+
end unless defined?(Ractor::Port)
|
|
357
|
+
else
|
|
358
|
+
class Ractor::Port
|
|
359
|
+
attr_reader :queue
|
|
360
|
+
|
|
361
|
+
def initialize
|
|
362
|
+
@queue = Queue.new
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
def <<(message)
|
|
366
|
+
Ractor::SELECT_MUTEX.synchronize {
|
|
367
|
+
@queue << message
|
|
368
|
+
Ractor::SELECT_CV.broadcast
|
|
369
|
+
}
|
|
370
|
+
self
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def receive
|
|
374
|
+
@queue.pop
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def close
|
|
378
|
+
Ractor::SELECT_MUTEX.synchronize {
|
|
379
|
+
@queue.close
|
|
380
|
+
Ractor::SELECT_CV.broadcast
|
|
381
|
+
}
|
|
382
|
+
self
|
|
383
|
+
end
|
|
384
|
+
end
|
|
385
|
+
end
|
data/test/run_tests.rb
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ractor/shim'
|
|
4
|
+
|
|
5
|
+
BUILTIN_SKIPS = [
|
|
6
|
+
"send shareable and unshareable objects", # leaks Ractors on 3.3
|
|
7
|
+
"Ractor::IsolationError cases", # Ractor.make_shareable too permissive on 3.4
|
|
8
|
+
"Ractor.select from two ractors.", # Ractor.select with ports doesn't work on 3.4
|
|
9
|
+
"SystemExit from a Ractor is re-raised", # Ractor::ClosedError on 3.4 instead of Ractor::RemoteError
|
|
10
|
+
"SystemExit from a Thread inside a Ractor is re-raised", # Ractor::ClosedError on 3.4 instead of Ractor::RemoteError
|
|
11
|
+
"Access to global-variables are prohibited (read)", # different error message on 3.4
|
|
12
|
+
"Access to global-variables are prohibited (write)", # different error message on 3.4
|
|
13
|
+
"Ractor.make_shareable(a_proc) is not supported now.", # no error on 3.4
|
|
14
|
+
"Ractor-local storage", # more permissive on 3.4
|
|
15
|
+
"Now NoMethodError is copyable", # fails on 3.4
|
|
16
|
+
"bind_call in Ractor [Bug #20934]", # SEGV on 3.4
|
|
17
|
+
"moved objects being corrupted if embeded (Hash)", # broken on 3.4
|
|
18
|
+
"moved objects being corrupted if embeded (MatchData)", # SEGV on 3.4
|
|
19
|
+
"moved objects being corrupted if embeded (Struct)", # broken on 3.4
|
|
20
|
+
"moved objects being corrupted if embeded (Object)", # broken on 3.4
|
|
21
|
+
"moved arrays can't be used", # broken on 3.4
|
|
22
|
+
"moved strings can't be used", # broken on 3.4
|
|
23
|
+
"moved hashes can't be used", # broken on 3.4
|
|
24
|
+
"move objects inside frozen containers", # broken on 3.4
|
|
25
|
+
"moved composite types move their non-shareable parts properly", # broken on 3.4
|
|
26
|
+
"Creating classes inside of Ractors", # OOM on 3.4
|
|
27
|
+
"Ractor#join raises RemoteError when the remote Ractor aborted with an exception", # needs join semantics for exceptions
|
|
28
|
+
"Only one Ractor can call Ractor#value", # need value semantics
|
|
29
|
+
"monitor port returns `:exited` when the monitering Ractor terminated.", # Ractor#monitor
|
|
30
|
+
"monitor port returns `:exited` even if the monitoring Ractor was terminated.", # Ractor#monitor
|
|
31
|
+
"monitor returns false if the monitoring Ractor was terminated.", # Ractor#monitor
|
|
32
|
+
"monitor port returns `:aborted` when the monitering Ractor is aborted.", # Ractor#monitor
|
|
33
|
+
"monitor port returns `:aborted` even if the monitoring Ractor was aborted.", # Ractor#monitor
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
SHIM_SKIPS = [
|
|
37
|
+
"Ractor.allocate is not supported",
|
|
38
|
+
"Ractor::IsolationError cases",
|
|
39
|
+
"$DEBUG, $VERBOSE are Ractor local", # mutates global variables like $DEBUG and $VERBOSE
|
|
40
|
+
"SystemExit from a Thread inside a Ractor is re-raised", # internal error on TruffleRuby
|
|
41
|
+
"unshareable object are copied", # expected due to no copy
|
|
42
|
+
"To copy the object, now Marshal#dump is used", # expected due to no copy
|
|
43
|
+
"send shareable and unshareable objects", # expected due to no copy
|
|
44
|
+
"frozen Objects are shareable", # expected due to no copy
|
|
45
|
+
"touching moved object causes an error", # expected due to no move
|
|
46
|
+
"move example2: Array", # expected due to no move
|
|
47
|
+
"Access to global-variables are prohibited (read)", # can't check
|
|
48
|
+
"Access to global-variables are prohibited (write)", # can't check
|
|
49
|
+
"$stdin,out,err is Ractor local, but shared fds", # can't do
|
|
50
|
+
"given block Proc will be isolated, so can not access outer variables.", # can't check
|
|
51
|
+
"ivar in shareable-objects are not allowed to access from non-main Ractor", # not checked
|
|
52
|
+
"ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (get)", # not checked
|
|
53
|
+
"ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (set)", # not checked
|
|
54
|
+
"and instance variables of classes/modules are accessible if they refer shareable objects", # not checked
|
|
55
|
+
"cvar in shareable-objects are not allowed to access from non-main Ractor", # can't check
|
|
56
|
+
"also cached cvar in shareable-objects are not allowed to access from non-main Ractor", # can't check
|
|
57
|
+
"Getting non-shareable objects via constants by other Ractors is not allowed", # can't check
|
|
58
|
+
"Constant cache should care about non-shareable constants", # can't check
|
|
59
|
+
"Setting non-shareable objects into constants by other Ractors is not allowed", # can't check
|
|
60
|
+
"define_method is not allowed", # can't check
|
|
61
|
+
"ObjectSpace._id2ref can not handle unshareable objects with Ractors", # can't check
|
|
62
|
+
"Ractor.make_shareable(obj)", # expected due to no freeze
|
|
63
|
+
"Ractor.make_shareable(obj) doesn't freeze shareable objects", # expected due to no freeze
|
|
64
|
+
"Ractor.make_shareable(a_proc) is not supported now.", # can't check
|
|
65
|
+
"Ractor.shareable?(recursive_objects)", # expected, due to all are shareable
|
|
66
|
+
"Ractor.make_shareable(recursive_objects)", # expected, due to all are shareable
|
|
67
|
+
"Ractor.make_shareable(obj, copy: true) makes copied shareable object.", # expected, due to all are shareable
|
|
68
|
+
"Can not trap with not isolated Proc on non-main ractor", # can't check
|
|
69
|
+
"Ractor-local storage with Thread inheritance of current Ractor", # hard
|
|
70
|
+
"Chilled strings are not shareable", # expected, due to all are shareable
|
|
71
|
+
"moved arrays can't be used", # expected due to no move
|
|
72
|
+
"moved strings can't be used", # expected due to no move
|
|
73
|
+
"moved hashes can't be used", # expected due to no move
|
|
74
|
+
"moved composite types move their non-shareable parts properly", # expected due to no move
|
|
75
|
+
"Only one Ractor can call Ractor#value", # could be implemented
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
GLOBAL_SKIPS = [
|
|
79
|
+
"threads in a ractor will killed", # leaks Ractors
|
|
80
|
+
"TracePoint with normal Proc should be Ractor local", # line numbers differ due to test harness
|
|
81
|
+
"Can yield back values while GC is sweeping [Bug #18117]", # too slow and leaks
|
|
82
|
+
"check experimental warning", # harness disables experimental warnings
|
|
83
|
+
"failed in autolaod in Ractor", # need more isolation
|
|
84
|
+
"fork after creating Ractor", # fork, leaks
|
|
85
|
+
"Ractors should be terminated after fork", # fork, hangs
|
|
86
|
+
"st_table, which can call malloc.", # leaks
|
|
87
|
+
"Creating classes inside of Ractors", # leaks
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
skips = GLOBAL_SKIPS
|
|
91
|
+
skips += BUILTIN_SKIPS if Ractor.builtin? && RUBY_VERSION < "3.5"
|
|
92
|
+
skips += SHIM_SKIPS if Ractor.shim?
|
|
93
|
+
if Ractor.builtin? && RUBY_VERSION < "3.4"
|
|
94
|
+
# fails on 3.3
|
|
95
|
+
skips += [
|
|
96
|
+
"unshareable frozen objects should still be frozen in new ractor after move",
|
|
97
|
+
"ivar in shareable-objects are not allowed to access from non-main Ractor",
|
|
98
|
+
"ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (get)",
|
|
99
|
+
"ivar in shareable-objects are not allowed to access from non-main Ractor, by @iv (set)",
|
|
100
|
+
900,
|
|
101
|
+
"moved objects have their shape properly set to original object's shape",
|
|
102
|
+
"Ractor-local storage with Thread inheritance of current Ractor",
|
|
103
|
+
"require in Ractor",
|
|
104
|
+
"autolaod in Ractor",
|
|
105
|
+
"moved objects being corrupted if embeded (String)",
|
|
106
|
+
"move object with generic ivar",
|
|
107
|
+
"move object with many generic ivars", # SEGV
|
|
108
|
+
"move object with complex generic ivars",
|
|
109
|
+
]
|
|
110
|
+
end
|
|
111
|
+
if Ractor.builtin? && RUBY_VERSION < "3.3"
|
|
112
|
+
# fails on 3.2
|
|
113
|
+
skips += [
|
|
114
|
+
"check moved object", # SEGV
|
|
115
|
+
]
|
|
116
|
+
end
|
|
117
|
+
if Ractor.builtin? && RUBY_VERSION < "3.1"
|
|
118
|
+
# fails on 3.0
|
|
119
|
+
skips += [
|
|
120
|
+
"and instance variables of classes/modules are accessible if they refer shareable objects",
|
|
121
|
+
"define_method is not allowed",
|
|
122
|
+
"check method cache invalidation", # SEGV
|
|
123
|
+
]
|
|
124
|
+
end
|
|
125
|
+
if Ractor.shim? && RUBY_ENGINE == "ruby" && RUBY_VERSION < "3.0"
|
|
126
|
+
# fails on 2.7
|
|
127
|
+
skips += [
|
|
128
|
+
"Ractor.count",
|
|
129
|
+
"fstring pool 2", # spurious, probably a fstring table bug
|
|
130
|
+
"check method cache invalidation", # syntax
|
|
131
|
+
]
|
|
132
|
+
end
|
|
133
|
+
if Ractor.shim? && RUBY_ENGINE == "jruby"
|
|
134
|
+
skips += [
|
|
135
|
+
"Ractor.count",
|
|
136
|
+
"ObjectSpace.each_object can not handle unshareable objects with Ractors",
|
|
137
|
+
"fstring pool 2", # spurious, probably a fstring table bug
|
|
138
|
+
]
|
|
139
|
+
end
|
|
140
|
+
if $DEBUG
|
|
141
|
+
# fails probably Due to Thread.abort_on_exception being true with $DEBUG true
|
|
142
|
+
skips += [
|
|
143
|
+
"an exception in a Ractor non-main thread will not be re-raised at Ractor#receive",
|
|
144
|
+
]
|
|
145
|
+
end
|
|
146
|
+
SKIPS = skips
|
|
147
|
+
|
|
148
|
+
def new_empty_binding
|
|
149
|
+
# we need a shareable self for shareable procs/lambdas, but also preserve the default definee of Object
|
|
150
|
+
Object.class_eval { binding }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def skip(reason = nil)
|
|
154
|
+
throw :skip, :skip
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
TEST_LINES = [""] + File.readlines("#{__dir__}/test_ractor.rb").map(&:chomp)
|
|
158
|
+
|
|
159
|
+
def generic_assert(expected, code, check, &error_message)
|
|
160
|
+
test_info = caller_locations(2, 1)[0]
|
|
161
|
+
line = test_info.lineno
|
|
162
|
+
test_name = TEST_LINES[line-1]
|
|
163
|
+
test_name = TEST_LINES[line-2] if test_name.start_with?("# [Bug")
|
|
164
|
+
warn "Could not find test name for test at line #{line}" unless test_name.start_with?("# ")
|
|
165
|
+
test_name = test_name[2..-1]
|
|
166
|
+
|
|
167
|
+
print "Running test from line #{line}: "
|
|
168
|
+
if SKIPS.include?(line) or SKIPS.include?(test_name)
|
|
169
|
+
puts "SKIP"
|
|
170
|
+
return
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
begin
|
|
174
|
+
actual = catch(:skip) do
|
|
175
|
+
eval(code, new_empty_binding, test_info.path, line).to_s
|
|
176
|
+
end
|
|
177
|
+
return if :skip == actual
|
|
178
|
+
rescue => e
|
|
179
|
+
puts "ERROR"
|
|
180
|
+
e.set_backtrace(e.backtrace.reject { |b|
|
|
181
|
+
b.include?(__FILE__)
|
|
182
|
+
}.map { |b|
|
|
183
|
+
b.sub(/(Object#)?new_empty_binding/, 'test')
|
|
184
|
+
})
|
|
185
|
+
raise e
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
if check.call(actual, expected)
|
|
189
|
+
puts "PASS #{actual}"
|
|
190
|
+
else
|
|
191
|
+
puts "FAIL"
|
|
192
|
+
raise RuntimeError, error_message.call(actual, expected), caller(2)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
wait_ms = 10
|
|
196
|
+
waited = 0
|
|
197
|
+
while (leaked = Ractor.count - 1) > 0 and waited < wait_ms
|
|
198
|
+
sleep 0.001
|
|
199
|
+
waited += 1
|
|
200
|
+
end
|
|
201
|
+
unless leaked == 0
|
|
202
|
+
raise "Test at line #{line} leaked #{leaked} Ractors"
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# cleanup global state
|
|
206
|
+
Object.send(:remove_const, :A) if Object.const_defined?(:A)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def assert_equal(expected, code, frozen_string_literal: nil)
|
|
210
|
+
generic_assert(expected, code, -> a, e { e == a }) { |actual, expected|
|
|
211
|
+
"Expected #{expected.inspect} but got #{actual.inspect}"
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def assert_match(expected, code, frozen_string_literal: nil)
|
|
216
|
+
generic_assert(expected, code, -> a, e { e.match?(a) }) { |actual, expected|
|
|
217
|
+
"Expected #{expected} =~ #{actual.inspect} but it was false"
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def assert_normal_exit(code)
|
|
222
|
+
# Skip because such tests need to run in their own process
|
|
223
|
+
return
|
|
224
|
+
# generic_assert(:unused, code, -> a, e { true }) { |actual, expected|
|
|
225
|
+
# raise "unreachable"
|
|
226
|
+
# }
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def yjit_enabled?
|
|
230
|
+
false
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
Warning[:experimental] = false
|
|
234
|
+
|
|
235
|
+
require_relative 'test_ractor'
|