rbs 1.4.0 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/Gemfile +1 -0
- data/core/builtin.rbs +1 -1
- data/core/file.rbs +3 -1
- data/core/global_variables.rbs +3 -3
- data/core/io/wait.rbs +37 -0
- data/core/io.rbs +4 -4
- data/core/ractor.rbs +779 -0
- data/core/string_io.rbs +3 -5
- data/lib/rbs/version.rb +1 -1
- data/stdlib/digest/0/digest.rbs +418 -0
- metadata +5 -2
data/core/ractor.rbs
ADDED
@@ -0,0 +1,779 @@
|
|
1
|
+
# Ractor is a Actor-model abstraction for Ruby that provides thread-safe
|
2
|
+
# parallel execution.
|
3
|
+
#
|
4
|
+
# Ractor.new can make new Ractor and it will run in parallel.
|
5
|
+
#
|
6
|
+
# # The simplest ractor
|
7
|
+
# r = Ractor.new {puts "I am in Ractor!"}
|
8
|
+
# r.take # wait it to finish
|
9
|
+
# # here "I am in Ractor!" would be printed
|
10
|
+
#
|
11
|
+
# Ractors do not share usual objects, so the some kind of thread-safety concerns
|
12
|
+
# such as data-race, race-conditions are not available on multi-ractor
|
13
|
+
# programming.
|
14
|
+
#
|
15
|
+
# To achieve this, ractors severely limit object sharing between different
|
16
|
+
# ractors. For example, unlike threads, ractors can't access each other's
|
17
|
+
# objects, nor any objects through variables of the outer scope.
|
18
|
+
#
|
19
|
+
# a = 1
|
20
|
+
# r = Ractor.new {puts "I am in Ractor! a=#{a}"}
|
21
|
+
# # fails immediately with
|
22
|
+
# # ArgumentError (can not isolate a Proc because it accesses outer variables (a).)
|
23
|
+
#
|
24
|
+
# On CRuby (the default implementation), Global Virtual Machine Lock (GVL) is
|
25
|
+
# held per ractor, so ractors are performed in parallel without locking each
|
26
|
+
# other.
|
27
|
+
#
|
28
|
+
# Instead of accessing the shared state, the objects should be passed to and
|
29
|
+
# from ractors via sending and receiving objects as messages.
|
30
|
+
#
|
31
|
+
# a = 1
|
32
|
+
# r = Ractor.new do
|
33
|
+
# a_in_ractor = receive # receive blocks till somebody will pass message
|
34
|
+
# puts "I am in Ractor! a=#{a_in_ractor}"
|
35
|
+
# end
|
36
|
+
# r.send(a) # pass it
|
37
|
+
# r.take
|
38
|
+
# # here "I am in Ractor! a=1" would be printed
|
39
|
+
#
|
40
|
+
# There are two pairs of methods for sending/receiving messages:
|
41
|
+
#
|
42
|
+
# * Ractor#send and Ractor.receive for when the *sender* knows the receiver
|
43
|
+
# (push);
|
44
|
+
# * Ractor.yield and Ractor#take for when the *receiver* knows the sender
|
45
|
+
# (pull);
|
46
|
+
#
|
47
|
+
#
|
48
|
+
# In addition to that, an argument to Ractor.new would be passed to block and
|
49
|
+
# available there as if received by Ractor.receive, and the last block value
|
50
|
+
# would be sent outside of the ractor as if sent by Ractor.yield.
|
51
|
+
#
|
52
|
+
# A little demonstration on a classic ping-pong:
|
53
|
+
#
|
54
|
+
# server = Ractor.new do
|
55
|
+
# puts "Server starts: #{self.inspect}"
|
56
|
+
# puts "Server sends: ping"
|
57
|
+
# Ractor.yield 'ping' # The server doesn't know the receiver and sends to whoever interested
|
58
|
+
# received = Ractor.receive # The server doesn't know the sender and receives from whoever sent
|
59
|
+
# puts "Server received: #{received}"
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# client = Ractor.new(server) do |srv| # The server is sent inside client, and available as srv
|
63
|
+
# puts "Client starts: #{self.inspect}"
|
64
|
+
# received = srv.take # The Client takes a message specifically from the server
|
65
|
+
# puts "Client received from " \
|
66
|
+
# "#{srv.inspect}: #{received}"
|
67
|
+
# puts "Client sends to " \
|
68
|
+
# "#{srv.inspect}: pong"
|
69
|
+
# srv.send 'pong' # The client sends a message specifically to the server
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# [client, server].each(&:take) # Wait till they both finish
|
73
|
+
#
|
74
|
+
# This will output:
|
75
|
+
#
|
76
|
+
# Server starts: #<Ractor:#2 test.rb:1 running>
|
77
|
+
# Server sends: ping
|
78
|
+
# Client starts: #<Ractor:#3 test.rb:8 running>
|
79
|
+
# Client received from #<Ractor:#2 rac.rb:1 blocking>: ping
|
80
|
+
# Client sends to #<Ractor:#2 rac.rb:1 blocking>: pong
|
81
|
+
# Server received: pong
|
82
|
+
#
|
83
|
+
# It is said that Ractor receives messages via the *incoming port*, and sends
|
84
|
+
# them to the *outgoing port*. Either one can be disabled with
|
85
|
+
# Ractor#close_incoming and Ractor#close_outgoing respectively. If a ractor
|
86
|
+
# terminated, its ports will be closed automatically.
|
87
|
+
#
|
88
|
+
# ## Shareable and unshareable objects
|
89
|
+
#
|
90
|
+
# When the object is sent to and from the ractor, it is important to understand
|
91
|
+
# whether the object is shareable or unshareable. Most of objects are
|
92
|
+
# unshareable objects.
|
93
|
+
#
|
94
|
+
# Shareable objects are basically those which can be used by several threads
|
95
|
+
# without compromising thread-safety; e.g. immutable ones. Ractor.shareable?
|
96
|
+
# allows to check this, and Ractor.make_shareable tries to make object shareable
|
97
|
+
# if it is not.
|
98
|
+
#
|
99
|
+
# Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are
|
100
|
+
# Ractor.shareable?('foo') #=> false, unless the string is frozen due to # freeze_string_literals: true
|
101
|
+
# Ractor.shareable?('foo'.freeze) #=> true
|
102
|
+
#
|
103
|
+
# ary = ['hello', 'world']
|
104
|
+
# ary.frozen? #=> false
|
105
|
+
# ary[0].frozen? #=> false
|
106
|
+
# Ractor.make_shareable(ary)
|
107
|
+
# ary.frozen? #=> true
|
108
|
+
# ary[0].frozen? #=> true
|
109
|
+
# ary[1].frozen? #=> true
|
110
|
+
#
|
111
|
+
# When a shareable object is sent (via #send or Ractor.yield), no additional
|
112
|
+
# processing happens, and it just becomes usable by both ractors. When an
|
113
|
+
# unshareable object is sent, it can be either *copied* or *moved*. The first is
|
114
|
+
# the default, and it makes the object's full copy by deep cloning of
|
115
|
+
# non-shareable parts of its structure.
|
116
|
+
#
|
117
|
+
# data = ['foo', 'bar'.freeze]
|
118
|
+
# r = Ractor.new do
|
119
|
+
# data2 = Ractor.receive
|
120
|
+
# puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}"
|
121
|
+
# end
|
122
|
+
# r.send(data)
|
123
|
+
# r.take
|
124
|
+
# puts "Outside : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}"
|
125
|
+
#
|
126
|
+
# This will output:
|
127
|
+
#
|
128
|
+
# In ractor: 340, 360, 320
|
129
|
+
# Outside : 380, 400, 320
|
130
|
+
#
|
131
|
+
# (Note that object id of both array and non-frozen string inside array have
|
132
|
+
# changed inside the ractor, showing it is different objects. But the second
|
133
|
+
# array's element, which is a shareable frozen string, has the same object_id.)
|
134
|
+
#
|
135
|
+
# Deep cloning of the objects may be slow, and sometimes impossible.
|
136
|
+
# Alternatively, `move: true` may be used on sending. This will *move* the
|
137
|
+
# object to the receiving ractor, making it inaccessible for a sending ractor.
|
138
|
+
#
|
139
|
+
# data = ['foo', 'bar']
|
140
|
+
# r = Ractor.new do
|
141
|
+
# data_in_ractor = Ractor.receive
|
142
|
+
# puts "In ractor: #{data_in_ractor.object_id}, #{data_in_ractor[0].object_id}"
|
143
|
+
# end
|
144
|
+
# r.send(data, move: true)
|
145
|
+
# r.take
|
146
|
+
# puts "Outside: moved? #{Ractor::MovedObject === data}"
|
147
|
+
# puts "Outside: #{data.inspect}"
|
148
|
+
#
|
149
|
+
# This will output:
|
150
|
+
#
|
151
|
+
# In ractor: 100, 120
|
152
|
+
# Outside: moved? true
|
153
|
+
# test.rb:9:in `method_missing': can not send any methods to a moved object (Ractor::MovedError)
|
154
|
+
#
|
155
|
+
# Notice that even `inspect` (and more basic methods like `__id__`) is
|
156
|
+
# inaccessible on a moved object.
|
157
|
+
#
|
158
|
+
# Besides frozen objects, there are shareable objects. Class and Module objects
|
159
|
+
# are shareable so the Class/Module definitons are shared between ractors.
|
160
|
+
# Ractor objects are also shareable objects. All operations for the shareable
|
161
|
+
# mutable objects are thread-safe, so the thread-safety property will be kept.
|
162
|
+
# We can not define mutable shareable objects in Ruby, but C extensions can
|
163
|
+
# introduce them.
|
164
|
+
#
|
165
|
+
# It is prohibited to access instance variables of mutable shareable objects
|
166
|
+
# (especially Modules and classes) from ractors other than main:
|
167
|
+
#
|
168
|
+
# class C
|
169
|
+
# class << self
|
170
|
+
# attr_accessor :tricky
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
#
|
174
|
+
# C.tricky = 'test'
|
175
|
+
#
|
176
|
+
# r = Ractor.new(C) do |cls|
|
177
|
+
# puts "I see #{cls}"
|
178
|
+
# puts "I can't see #{cls.tricky}"
|
179
|
+
# end
|
180
|
+
# r.take
|
181
|
+
# # I see C
|
182
|
+
# # can not access instance variables of classes/modules from non-main Ractors (RuntimeError)
|
183
|
+
#
|
184
|
+
# Ractors can access constants if they are shareable. The main Ractor is the
|
185
|
+
# only one that can access non-shareable constants.
|
186
|
+
#
|
187
|
+
# GOOD = 'good'.freeze
|
188
|
+
# BAD = 'bad'
|
189
|
+
#
|
190
|
+
# r = Ractor.new do
|
191
|
+
# puts "GOOD=#{GOOD}"
|
192
|
+
# puts "BAD=#{BAD}"
|
193
|
+
# end
|
194
|
+
# r.take
|
195
|
+
# # GOOD=good
|
196
|
+
# # can not access non-shareable objects in constant Object::BAD by non-main Ractor. (NameError)
|
197
|
+
#
|
198
|
+
# # Consider the same C class from above
|
199
|
+
#
|
200
|
+
# r = Ractor.new do
|
201
|
+
# puts "I see #{C}"
|
202
|
+
# puts "I can't see #{C.tricky}"
|
203
|
+
# end
|
204
|
+
# r.take
|
205
|
+
# # I see C
|
206
|
+
# # can not access instance variables of classes/modules from non-main Ractors (RuntimeError)
|
207
|
+
#
|
208
|
+
# See also the description of `# shareable_constant_value` pragma in [Comments
|
209
|
+
# syntax](rdoc-ref:doc/syntax/comments.rdoc) explanation.
|
210
|
+
#
|
211
|
+
# ## Ractors vs threads
|
212
|
+
#
|
213
|
+
# Each ractor creates its own thread. New threads can be created from inside
|
214
|
+
# ractor (and, on CRuby, sharing GVL with other threads of this ractor).
|
215
|
+
#
|
216
|
+
# r = Ractor.new do
|
217
|
+
# a = 1
|
218
|
+
# Thread.new {puts "Thread in ractor: a=#{a}"}.join
|
219
|
+
# end
|
220
|
+
# r.take
|
221
|
+
# # Here "Thread in ractor: a=1" will be printed
|
222
|
+
#
|
223
|
+
# ## Note on code examples
|
224
|
+
#
|
225
|
+
# In examples below, sometimes we use the following method to wait till ractors
|
226
|
+
# that are not currently blocked will finish (or process till next blocking)
|
227
|
+
# method.
|
228
|
+
#
|
229
|
+
# def wait
|
230
|
+
# sleep(0.1)
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
# It is **only for demonstration purposes** and shouldn't be used in a real
|
234
|
+
# code. Most of the times, just #take is used to wait till ractor will finish.
|
235
|
+
#
|
236
|
+
# ## Reference
|
237
|
+
#
|
238
|
+
# See [Ractor desgin doc](rdoc-ref:doc/ractor.md) for more details.
|
239
|
+
class Ractor
|
240
|
+
# Returns total count of Ractors currently running.
|
241
|
+
#
|
242
|
+
# Ractor.count #=> 1
|
243
|
+
# r = Ractor.new(name: 'example') { Ractor.yield(1) }
|
244
|
+
# Ractor.count #=> 2 (main + example ractor)
|
245
|
+
# r.take # wait for Ractor.yield(1)
|
246
|
+
# r.take # wait till r will finish
|
247
|
+
# Ractor.count #=> 1
|
248
|
+
#
|
249
|
+
def self.count: () -> Integer
|
250
|
+
|
251
|
+
# Returns the currently executing Ractor.
|
252
|
+
#
|
253
|
+
# Ractor.current #=> #<Ractor:#1 running>
|
254
|
+
#
|
255
|
+
def self.current: () -> untyped
|
256
|
+
|
257
|
+
# returns main ractor
|
258
|
+
#
|
259
|
+
def self.main: () -> untyped
|
260
|
+
|
261
|
+
# Make `obj` shareable between ractors.
|
262
|
+
#
|
263
|
+
# `obj` and all the objects it refers to will be frozen, unless they are already
|
264
|
+
# shareable.
|
265
|
+
#
|
266
|
+
# If `copy` keyword is `true`, the method will copy objects before freezing them
|
267
|
+
# This is safer option but it can take be slower.
|
268
|
+
#
|
269
|
+
# Note that the specification and implementation of this method are not mature
|
270
|
+
# and may be changed in the future.
|
271
|
+
#
|
272
|
+
# obj = ['test']
|
273
|
+
# Ractor.shareable?(obj) #=> false
|
274
|
+
# Ractor.make_shareable(obj) #=> ["test"]
|
275
|
+
# Ractor.shareable?(obj) #=> true
|
276
|
+
# obj.frozen? #=> true
|
277
|
+
# obj[0].frozen? #=> true
|
278
|
+
#
|
279
|
+
# # Copy vs non-copy versions:
|
280
|
+
# obj1 = ['test']
|
281
|
+
# obj1s = Ractor.make_shareable(obj1)
|
282
|
+
# obj1.frozen? #=> true
|
283
|
+
# obj1s.object_id == obj1.object_id #=> true
|
284
|
+
# obj2 = ['test']
|
285
|
+
# obj2s = Ractor.make_shareable(obj2, copy: true)
|
286
|
+
# obj2.frozen? #=> false
|
287
|
+
# obj2s.frozen? #=> true
|
288
|
+
# obj2s.object_id == obj2.object_id #=> false
|
289
|
+
# obj2s[0].object_id == obj2[0].object_id #=> false
|
290
|
+
#
|
291
|
+
# See also the "Shareable and unshareable objects" section in the Ractor class
|
292
|
+
# docs.
|
293
|
+
#
|
294
|
+
def self.make_shareable: [T] (T obj, ?copy: boolish) -> T
|
295
|
+
|
296
|
+
# Create a new Ractor with args and a block.
|
297
|
+
#
|
298
|
+
# A block (Proc) will be isolated (can't access to outer variables). `self`
|
299
|
+
# inside the block will refer to the current Ractor.
|
300
|
+
#
|
301
|
+
# r = Ractor.new { puts "Hi, I am #{self.inspect}" }
|
302
|
+
# r.take
|
303
|
+
# # Prints "Hi, I am #<Ractor:#2 test.rb:1 running>"
|
304
|
+
#
|
305
|
+
# `args` passed to the method would be propagated to block args by the same
|
306
|
+
# rules as objects passed through #send/Ractor.receive: if `args` are not
|
307
|
+
# shareable, they will be copied (via deep cloning, which might be inefficient).
|
308
|
+
#
|
309
|
+
# arg = [1, 2, 3]
|
310
|
+
# puts "Passing: #{arg} (##{arg.object_id})"
|
311
|
+
# r = Ractor.new(arg) {|received_arg|
|
312
|
+
# puts "Received: #{received_arg} (##{received_arg.object_id})"
|
313
|
+
# }
|
314
|
+
# r.take
|
315
|
+
# # Prints:
|
316
|
+
# # Passing: [1, 2, 3] (#280)
|
317
|
+
# # Received: [1, 2, 3] (#300)
|
318
|
+
#
|
319
|
+
# Ractor's `name` can be set for debugging purposes:
|
320
|
+
#
|
321
|
+
# r = Ractor.new(name: 'my ractor') {}
|
322
|
+
# p r
|
323
|
+
# #=> #<Ractor:#3 my ractor test.rb:1 terminated>
|
324
|
+
#
|
325
|
+
def self.new: (*untyped args, ?name: string) { (*untyped) -> untyped } -> Ractor
|
326
|
+
|
327
|
+
# Receive an incoming message from the current Ractor's incoming port's queue,
|
328
|
+
# which was sent there by #send.
|
329
|
+
#
|
330
|
+
# r = Ractor.new do
|
331
|
+
# v1 = Ractor.receive
|
332
|
+
# puts "Received: #{v1}"
|
333
|
+
# end
|
334
|
+
# r.send('message1')
|
335
|
+
# r.take
|
336
|
+
# # Here will be printed: "Received: message1"
|
337
|
+
#
|
338
|
+
# Alternatively, private instance method `receive` may be used:
|
339
|
+
#
|
340
|
+
# r = Ractor.new do
|
341
|
+
# v1 = receive
|
342
|
+
# puts "Received: #{v1}"
|
343
|
+
# end
|
344
|
+
# r.send('message1')
|
345
|
+
# r.take
|
346
|
+
# # Here will be printed: "Received: message1"
|
347
|
+
#
|
348
|
+
# The method blocks if the queue is empty.
|
349
|
+
#
|
350
|
+
# r = Ractor.new do
|
351
|
+
# puts "Before first receive"
|
352
|
+
# v1 = Ractor.receive
|
353
|
+
# puts "Received: #{v1}"
|
354
|
+
# v2 = Ractor.receive
|
355
|
+
# puts "Received: #{v2}"
|
356
|
+
# end
|
357
|
+
# wait
|
358
|
+
# puts "Still not received"
|
359
|
+
# r.send('message1')
|
360
|
+
# wait
|
361
|
+
# puts "Still received only one"
|
362
|
+
# r.send('message2')
|
363
|
+
# r.take
|
364
|
+
#
|
365
|
+
# Output:
|
366
|
+
#
|
367
|
+
# Before first receive
|
368
|
+
# Still not received
|
369
|
+
# Received: message1
|
370
|
+
# Still received only one
|
371
|
+
# Received: message2
|
372
|
+
#
|
373
|
+
# If close_incoming was called on the ractor, the method raises
|
374
|
+
# Ractor::ClosedError if there are no more messages in incoming queue:
|
375
|
+
#
|
376
|
+
# Ractor.new do
|
377
|
+
# close_incoming
|
378
|
+
# receive
|
379
|
+
# end
|
380
|
+
# wait
|
381
|
+
# # in `receive': The incoming port is already closed => #<Ractor:#2 test.rb:1 running> (Ractor::ClosedError)
|
382
|
+
#
|
383
|
+
def self.receive: () -> untyped
|
384
|
+
|
385
|
+
# Receive only a specific message.
|
386
|
+
#
|
387
|
+
# Instead of Ractor.receive, Ractor.receive_if can provide a pattern by a block
|
388
|
+
# and you can choose the receiving message.
|
389
|
+
#
|
390
|
+
# r = Ractor.new do
|
391
|
+
# p Ractor.receive_if{|msg| msg.match?(/foo/)} #=> "foo3"
|
392
|
+
# p Ractor.receive_if{|msg| msg.match?(/bar/)} #=> "bar1"
|
393
|
+
# p Ractor.receive_if{|msg| msg.match?(/baz/)} #=> "baz2"
|
394
|
+
# end
|
395
|
+
# r << "bar1"
|
396
|
+
# r << "baz2"
|
397
|
+
# r << "foo3"
|
398
|
+
# r.take
|
399
|
+
#
|
400
|
+
# This will output:
|
401
|
+
#
|
402
|
+
# foo3
|
403
|
+
# bar1
|
404
|
+
# baz2
|
405
|
+
#
|
406
|
+
# If the block returns a truthy value, the message will be removed from the
|
407
|
+
# incoming queue and returned. Otherwise, the messsage remains in the incoming
|
408
|
+
# queue and the following received messages are checked by the given block.
|
409
|
+
#
|
410
|
+
# If there are no messages left in the incoming queue, the method will block
|
411
|
+
# until new messages arrive.
|
412
|
+
#
|
413
|
+
# If the block is escaped by break/return/exception/throw, the message is
|
414
|
+
# removed from the incoming queue as if a truthy value had been returned.
|
415
|
+
#
|
416
|
+
# r = Ractor.new do
|
417
|
+
# val = Ractor.receive_if{|msg| msg.is_a?(Array)}
|
418
|
+
# puts "Received successfully: #{val}"
|
419
|
+
# end
|
420
|
+
#
|
421
|
+
# r.send(1)
|
422
|
+
# r.send('test')
|
423
|
+
# wait
|
424
|
+
# puts "2 non-matching sent, nothing received"
|
425
|
+
# r.send([1, 2, 3])
|
426
|
+
# wait
|
427
|
+
#
|
428
|
+
# Prints:
|
429
|
+
#
|
430
|
+
# 2 non-matching sent, nothing received
|
431
|
+
# Received successfully: [1, 2, 3]
|
432
|
+
#
|
433
|
+
# Note that you can not call receive/receive_if in the given block recursively.
|
434
|
+
# It means that you should not do any tasks in the block.
|
435
|
+
#
|
436
|
+
# Ractor.current << true
|
437
|
+
# Ractor.receive_if{|msg| Ractor.receive}
|
438
|
+
# #=> `receive': can not call receive/receive_if recursively (Ractor::Error)
|
439
|
+
#
|
440
|
+
def self.receive_if: () { (untyped) -> boolish } -> untyped
|
441
|
+
|
442
|
+
alias self.recv self.receive
|
443
|
+
|
444
|
+
# Waits for the first ractor to have something in its outgoing port, reads from
|
445
|
+
# this ractor, and returns that ractor and the object received.
|
446
|
+
#
|
447
|
+
# r1 = Ractor.new {Ractor.yield 'from 1'}
|
448
|
+
# r2 = Ractor.new {Ractor.yield 'from 2'}
|
449
|
+
#
|
450
|
+
# r, obj = Ractor.select(r1, r2)
|
451
|
+
#
|
452
|
+
# puts "received #{obj.inspect} from #{r.inspect}"
|
453
|
+
# # Prints: received "from 1" from #<Ractor:#2 test.rb:1 running>
|
454
|
+
#
|
455
|
+
# If one of the given ractors is the current ractor, and it would be selected,
|
456
|
+
# `r` will contain `:receive` symbol instead of the ractor object.
|
457
|
+
#
|
458
|
+
# r1 = Ractor.new(Ractor.current) do |main|
|
459
|
+
# main.send 'to main'
|
460
|
+
# Ractor.yield 'from 1'
|
461
|
+
# end
|
462
|
+
# r2 = Ractor.new do
|
463
|
+
# Ractor.yield 'from 2'
|
464
|
+
# end
|
465
|
+
#
|
466
|
+
# r, obj = Ractor.select(r1, r2, Ractor.current)
|
467
|
+
# puts "received #{obj.inspect} from #{r.inspect}"
|
468
|
+
# # Prints: received "to main" from :receive
|
469
|
+
#
|
470
|
+
# If `yield_value` is provided, that value may be yielded if another Ractor is
|
471
|
+
# calling #take. In this case, the pair `[:yield, nil]` would be returned:
|
472
|
+
#
|
473
|
+
# r1 = Ractor.new(Ractor.current) do |main|
|
474
|
+
# puts "Received from main: #{main.take}"
|
475
|
+
# end
|
476
|
+
#
|
477
|
+
# puts "Trying to select"
|
478
|
+
# r, obj = Ractor.select(r1, Ractor.current, yield_value: 123)
|
479
|
+
# wait
|
480
|
+
# puts "Received #{obj.inspect} from #{r.inspect}"
|
481
|
+
#
|
482
|
+
# This will print:
|
483
|
+
#
|
484
|
+
# Trying to select
|
485
|
+
# Received from main: 123
|
486
|
+
# Received nil from :yield
|
487
|
+
#
|
488
|
+
# `move` boolean flag defines whether yielded value should be copied (default)
|
489
|
+
# or moved.
|
490
|
+
#
|
491
|
+
def self.select: (*Ractor ractors, ?move: boolish, ?yield_value: untyped) -> [Ractor | Symbol, untyped]
|
492
|
+
|
493
|
+
# Checks if the object is shareable by ractors.
|
494
|
+
#
|
495
|
+
# Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are frozen
|
496
|
+
# Ractor.shareable?('foo') #=> false, unless the string is frozen due to # freeze_string_literals: true
|
497
|
+
# Ractor.shareable?('foo'.freeze) #=> true
|
498
|
+
#
|
499
|
+
# See also the "Shareable and unshareable objects" section in the Ractor class
|
500
|
+
# docs.
|
501
|
+
#
|
502
|
+
def self.shareable?: (untyped obj) -> bool
|
503
|
+
|
504
|
+
# Send a message to the current ractor's outgoing port to be consumed by #take.
|
505
|
+
#
|
506
|
+
# r = Ractor.new {Ractor.yield 'Hello from ractor'}
|
507
|
+
# puts r.take
|
508
|
+
# # Prints: "Hello from ractor"
|
509
|
+
#
|
510
|
+
# The method is blocking, and will return only when somebody consumes the sent
|
511
|
+
# message.
|
512
|
+
#
|
513
|
+
# r = Ractor.new do
|
514
|
+
# Ractor.yield 'Hello from ractor'
|
515
|
+
# puts "Ractor: after yield"
|
516
|
+
# end
|
517
|
+
# wait
|
518
|
+
# puts "Still not taken"
|
519
|
+
# puts r.take
|
520
|
+
#
|
521
|
+
# This will print:
|
522
|
+
#
|
523
|
+
# Still not taken
|
524
|
+
# Hello from ractor
|
525
|
+
# Ractor: after yield
|
526
|
+
#
|
527
|
+
# If the outgoing port was closed with #close_outgoing, the method will raise:
|
528
|
+
#
|
529
|
+
# r = Ractor.new do
|
530
|
+
# close_outgoing
|
531
|
+
# Ractor.yield 'Hello from ractor'
|
532
|
+
# end
|
533
|
+
# wait
|
534
|
+
# # `yield': The outgoing-port is already closed (Ractor::ClosedError)
|
535
|
+
#
|
536
|
+
# The meaning of `move` argument is the same as for #send.
|
537
|
+
#
|
538
|
+
def self.yield: (untyped obj, ?move: boolish) -> untyped
|
539
|
+
|
540
|
+
public
|
541
|
+
|
542
|
+
alias << send
|
543
|
+
|
544
|
+
# get a value from ractor-local storage
|
545
|
+
#
|
546
|
+
def []: (Symbol | String sym) -> untyped
|
547
|
+
|
548
|
+
# set a value in ractor-local storage
|
549
|
+
#
|
550
|
+
def []=: [T] (Symbol | String sym, T val) -> T
|
551
|
+
|
552
|
+
# Closes the incoming port and returns its previous state. All further attempts
|
553
|
+
# to Ractor.receive in the ractor, and #send to the ractor will fail with
|
554
|
+
# Ractor::ClosedError.
|
555
|
+
#
|
556
|
+
# r = Ractor.new {sleep(500)}
|
557
|
+
# r.close_incoming #=> false
|
558
|
+
# r.close_incoming #=> true
|
559
|
+
# r.send('test')
|
560
|
+
# # Ractor::ClosedError (The incoming-port is already closed)
|
561
|
+
#
|
562
|
+
def close_incoming: () -> bool
|
563
|
+
|
564
|
+
# Closes the outgoing port and returns its previous state. All further attempts
|
565
|
+
# to Ractor.yield in the ractor, and #take from the ractor will fail with
|
566
|
+
# Ractor::ClosedError.
|
567
|
+
#
|
568
|
+
# r = Ractor.new {sleep(500)}
|
569
|
+
# r.close_outgoing #=> false
|
570
|
+
# r.close_outgoing #=> true
|
571
|
+
# r.take
|
572
|
+
# # Ractor::ClosedError (The outgoing-port is already closed)
|
573
|
+
#
|
574
|
+
def close_outgoing: () -> bool
|
575
|
+
|
576
|
+
def inspect: () -> String
|
577
|
+
|
578
|
+
# The name set in Ractor.new, or `nil`.
|
579
|
+
#
|
580
|
+
def name: () -> String?
|
581
|
+
|
582
|
+
# Send a message to a Ractor's incoming queue to be consumed by Ractor.receive.
|
583
|
+
#
|
584
|
+
# r = Ractor.new do
|
585
|
+
# value = Ractor.receive
|
586
|
+
# puts "Received #{value}"
|
587
|
+
# end
|
588
|
+
# r.send 'message'
|
589
|
+
# # Prints: "Received: message"
|
590
|
+
#
|
591
|
+
# The method is non-blocking (will return immediately even if the ractor is not
|
592
|
+
# ready to receive anything):
|
593
|
+
#
|
594
|
+
# r = Ractor.new {sleep(5)}
|
595
|
+
# r.send('test')
|
596
|
+
# puts "Sent successfully"
|
597
|
+
# # Prints: "Sent successfully" immediately
|
598
|
+
#
|
599
|
+
# Attempt to send to ractor which already finished its execution will raise
|
600
|
+
# Ractor::ClosedError.
|
601
|
+
#
|
602
|
+
# r = Ractor.new {}
|
603
|
+
# r.take
|
604
|
+
# p r
|
605
|
+
# # "#<Ractor:#6 (irb):23 terminated>"
|
606
|
+
# r.send('test')
|
607
|
+
# # Ractor::ClosedError (The incoming-port is already closed)
|
608
|
+
#
|
609
|
+
# If close_incoming was called on the ractor, the method also raises
|
610
|
+
# Ractor::ClosedError.
|
611
|
+
#
|
612
|
+
# r = Ractor.new do
|
613
|
+
# sleep(500)
|
614
|
+
# receive
|
615
|
+
# end
|
616
|
+
# r.close_incoming
|
617
|
+
# r.send('test')
|
618
|
+
# # Ractor::ClosedError (The incoming-port is already closed)
|
619
|
+
# # The error would be raised immediately, not when ractor will try to receive
|
620
|
+
#
|
621
|
+
# If the `obj` is unshareable, by default it would be copied into ractor by deep
|
622
|
+
# cloning. If the `move: true` is passed, object is *moved* into ractor and
|
623
|
+
# becomes inaccessible to sender.
|
624
|
+
#
|
625
|
+
# r = Ractor.new {puts "Received: #{receive}"}
|
626
|
+
# msg = 'message'
|
627
|
+
# r.send(msg, move: true)
|
628
|
+
# r.take
|
629
|
+
# p msg
|
630
|
+
#
|
631
|
+
# This prints:
|
632
|
+
#
|
633
|
+
# Received: message
|
634
|
+
# in `p': undefined method `inspect' for #<Ractor::MovedObject:0x000055c99b9b69b8>
|
635
|
+
#
|
636
|
+
# All references to the object and its parts will become invalid in sender.
|
637
|
+
#
|
638
|
+
# r = Ractor.new {puts "Received: #{receive}"}
|
639
|
+
# s = 'message'
|
640
|
+
# ary = [s]
|
641
|
+
# copy = ary.dup
|
642
|
+
# r.send(ary, move: true)
|
643
|
+
#
|
644
|
+
# s.inspect
|
645
|
+
# # Ractor::MovedError (can not send any methods to a moved object)
|
646
|
+
# ary.class
|
647
|
+
# # Ractor::MovedError (can not send any methods to a moved object)
|
648
|
+
# copy.class
|
649
|
+
# # => Array, it is different object
|
650
|
+
# copy[0].inspect
|
651
|
+
# # Ractor::MovedError (can not send any methods to a moved object)
|
652
|
+
# # ...but its item was still a reference to `s`, which was moved
|
653
|
+
#
|
654
|
+
# If the object was shareable, `move: true` has no effect on it:
|
655
|
+
#
|
656
|
+
# r = Ractor.new {puts "Received: #{receive}"}
|
657
|
+
# s = 'message'.freeze
|
658
|
+
# r.send(s, move: true)
|
659
|
+
# s.inspect #=> "message", still available
|
660
|
+
#
|
661
|
+
def send: (untyped obj, ?move: boolish) -> Ractor
|
662
|
+
|
663
|
+
# Take a message from ractor's outgoing port, which was put there by
|
664
|
+
# Ractor.yield or at ractor's finalization.
|
665
|
+
#
|
666
|
+
# r = Ractor.new do
|
667
|
+
# Ractor.yield 'explicit yield'
|
668
|
+
# 'last value'
|
669
|
+
# end
|
670
|
+
# puts r.take #=> 'explicit yield'
|
671
|
+
# puts r.take #=> 'last value'
|
672
|
+
# puts r.take # Ractor::ClosedError (The outgoing-port is already closed)
|
673
|
+
#
|
674
|
+
# The fact that the last value is also put to outgoing port means that `take`
|
675
|
+
# can be used as some analog of Thread#join ("just wait till ractor finishes"),
|
676
|
+
# but don't forget it will raise if somebody had already consumed everything
|
677
|
+
# ractor have produced.
|
678
|
+
#
|
679
|
+
# If the outgoing port was closed with #close_outgoing, the method will raise
|
680
|
+
# Ractor::ClosedError.
|
681
|
+
#
|
682
|
+
# r = Ractor.new do
|
683
|
+
# sleep(500)
|
684
|
+
# Ractor.yield 'Hello from ractor'
|
685
|
+
# end
|
686
|
+
# r.close_outgoing
|
687
|
+
# r.take
|
688
|
+
# # Ractor::ClosedError (The outgoing-port is already closed)
|
689
|
+
# # The error would be raised immediately, not when ractor will try to receive
|
690
|
+
#
|
691
|
+
# If an uncaught exception is raised in the Ractor, it is propagated on take as
|
692
|
+
# a Ractor::RemoteError.
|
693
|
+
#
|
694
|
+
# r = Ractor.new {raise "Something weird happened"}
|
695
|
+
#
|
696
|
+
# begin
|
697
|
+
# r.take
|
698
|
+
# rescue => e
|
699
|
+
# p e # => #<Ractor::RemoteError: thrown by remote Ractor.>
|
700
|
+
# p e.ractor == r # => true
|
701
|
+
# p e.cause # => #<RuntimeError: Something weird happened>
|
702
|
+
# end
|
703
|
+
#
|
704
|
+
# Ractor::ClosedError is a descendant of StopIteration, so the closing of the
|
705
|
+
# ractor will break the loops without propagating the error:
|
706
|
+
#
|
707
|
+
# r = Ractor.new do
|
708
|
+
# 3.times {|i| Ractor.yield "message #{i}"}
|
709
|
+
# "finishing"
|
710
|
+
# end
|
711
|
+
#
|
712
|
+
# loop {puts "Received: " + r.take}
|
713
|
+
# puts "Continue successfully"
|
714
|
+
#
|
715
|
+
# This will print:
|
716
|
+
#
|
717
|
+
# Received: message 0
|
718
|
+
# Received: message 1
|
719
|
+
# Received: message 2
|
720
|
+
# Received: finishing
|
721
|
+
# Continue successfully
|
722
|
+
#
|
723
|
+
def take: () -> untyped
|
724
|
+
|
725
|
+
alias to_s inspect
|
726
|
+
|
727
|
+
private
|
728
|
+
|
729
|
+
# same as Ractor.receive
|
730
|
+
#
|
731
|
+
def receive: () -> untyped
|
732
|
+
|
733
|
+
def receive_if: () { (untyped) -> boolish } -> untyped
|
734
|
+
|
735
|
+
alias recv receive
|
736
|
+
|
737
|
+
class ClosedError < StopIteration
|
738
|
+
end
|
739
|
+
|
740
|
+
class Error < RuntimeError
|
741
|
+
end
|
742
|
+
|
743
|
+
class IsolationError < Ractor::Error
|
744
|
+
end
|
745
|
+
|
746
|
+
class MovedError < Ractor::Error
|
747
|
+
end
|
748
|
+
|
749
|
+
class MovedObject < BasicObject
|
750
|
+
public
|
751
|
+
|
752
|
+
def !: (*untyped) -> untyped
|
753
|
+
|
754
|
+
def !=: (*untyped) -> untyped
|
755
|
+
|
756
|
+
def ==: (*untyped) -> untyped
|
757
|
+
|
758
|
+
def __id__: (*untyped) -> untyped
|
759
|
+
|
760
|
+
def __send__: (*untyped) -> untyped
|
761
|
+
|
762
|
+
def equal?: (*untyped) -> untyped
|
763
|
+
|
764
|
+
def instance_eval: (*untyped) -> untyped
|
765
|
+
|
766
|
+
def instance_exec: (*untyped) -> untyped
|
767
|
+
|
768
|
+
def method_missing: (*untyped) -> untyped
|
769
|
+
end
|
770
|
+
|
771
|
+
class RemoteError < Ractor::Error
|
772
|
+
public
|
773
|
+
|
774
|
+
def ractor: () -> Ractor
|
775
|
+
end
|
776
|
+
|
777
|
+
class UnsafeError < Ractor::Error
|
778
|
+
end
|
779
|
+
end
|