Remove prism compiler warning
[ruby.git] / ractor.rb
blobd18062ca07991961076079c42e322df065c9f594
1 # \Ractor is an Actor-model abstraction for Ruby that provides thread-safe parallel execution.
3 # Ractor.new makes a new \Ractor, which can run in parallel.
5 #     # The simplest ractor
6 #     r = Ractor.new {puts "I am in Ractor!"}
7 #     r.take # wait for it to finish
8 #     # Here, "I am in Ractor!" is printed
10 # Ractors do not share all objects with each other. There are two main benefits to this: across ractors, thread-safety
11 # concerns such as data-races and race-conditions are not possible. The other benefit is parallelism.
13 # To achieve this, object sharing is limited across ractors.
14 # For example, unlike in threads, ractors can't access all the objects available in other ractors. Even objects normally
15 # available through variables in the outer scope are prohibited from being used across ractors.
17 #     a = 1
18 #     r = Ractor.new {puts "I am in Ractor! a=#{a}"}
19 #     # fails immediately with
20 #     # ArgumentError (can not isolate a Proc because it accesses outer variables (a).)
22 # The object must be explicitly shared:
23 #     a = 1
24 #     r = Ractor.new(a) { |a1| puts "I am in Ractor! a=#{a1}"}
26 # On CRuby (the default implementation), Global Virtual Machine Lock (GVL) is held per ractor, so
27 # ractors can perform in parallel without locking each other. This is unlike the situation with threads
28 # on CRuby.
30 # Instead of accessing shared state, objects should be passed to and from ractors by
31 # sending and receiving them as messages.
33 #     a = 1
34 #     r = Ractor.new do
35 #       a_in_ractor = receive # receive blocks until somebody passes a message
36 #       puts "I am in Ractor! a=#{a_in_ractor}"
37 #     end
38 #     r.send(a)  # pass it
39 #     r.take
40 #     # Here, "I am in Ractor! a=1" is printed
42 # There are two pairs of methods for sending/receiving messages:
44 # * Ractor#send and Ractor.receive for when the _sender_ knows the receiver (push);
45 # * Ractor.yield and Ractor#take for when the _receiver_ knows the sender (pull);
47 # In addition to that, any arguments passed to Ractor.new are passed to the block and available there
48 # as if received by Ractor.receive, and the last block value is sent outside of the
49 # ractor as if sent by Ractor.yield.
51 # A little demonstration of a classic ping-pong:
53 #     server = Ractor.new(name: "server") do
54 #       puts "Server starts: #{self.inspect}"
55 #       puts "Server sends: ping"
56 #       Ractor.yield 'ping'                       # The server doesn't know the receiver and sends to whoever interested
57 #       received = Ractor.receive                 # The server doesn't know the sender and receives from whoever sent
58 #       puts "Server received: #{received}"
59 #     end
61 #     client = Ractor.new(server) do |srv|        # The server is sent to the client, and available as srv
62 #       puts "Client starts: #{self.inspect}"
63 #       received = srv.take                       # The client takes a message from the server
64 #       puts "Client received from " \
65 #            "#{srv.inspect}: #{received}"
66 #       puts "Client sends to " \
67 #            "#{srv.inspect}: pong"
68 #       srv.send 'pong'                           # The client sends a message to the server
69 #     end
71 #     [client, server].each(&:take)               # Wait until they both finish
73 # This will output something like:
75 #     Server starts: #<Ractor:#2 server test.rb:1 running>
76 #     Server sends: ping
77 #     Client starts: #<Ractor:#3 test.rb:8 running>
78 #     Client received from #<Ractor:#2 server test.rb:1 blocking>: ping
79 #     Client sends to #<Ractor:#2 server test.rb:1 blocking>: pong
80 #     Server received: pong
82 # Ractors receive their messages via the <em>incoming port</em>, and send them
83 # to the <em>outgoing port</em>. Either one can be disabled with Ractor#close_incoming and
84 # Ractor#close_outgoing, respectively. When a ractor terminates, its ports are closed
85 # automatically.
87 # == Shareable and unshareable objects
89 # When an object is sent to and from a ractor, it's important to understand whether the
90 # object is shareable or unshareable. Most Ruby objects are unshareable objects. Even
91 # frozen objects can be unshareable if they contain (through their instance variables) unfrozen
92 # objects.
94 # Shareable objects are those which can be used by several threads without compromising
95 # thread-safety, for example numbers, +true+ and +false+. Ractor.shareable? allows you to check this,
96 # and Ractor.make_shareable tries to make the object shareable if it's not already, and gives an error
97 # if it can't do it.
99 #     Ractor.shareable?(1)            #=> true -- numbers and other immutable basic values are shareable
100 #     Ractor.shareable?('foo')        #=> false, unless the string is frozen due to # frozen_string_literal: true
101 #     Ractor.shareable?('foo'.freeze) #=> true
102 #     Ractor.shareable?([Object.new].freeze) #=> false, inner object is unfrozen
104 #     ary = ['hello', 'world']
105 #     ary.frozen?                 #=> false
106 #     ary[0].frozen?              #=> false
107 #     Ractor.make_shareable(ary)
108 #     ary.frozen?                 #=> true
109 #     ary[0].frozen?              #=> true
110 #     ary[1].frozen?              #=> true
112 # When a shareable object is sent (via #send or Ractor.yield), no additional processing occurs
113 # on it. It just becomes usable by both ractors. When an unshareable object is sent, it can be
114 # either _copied_ or _moved_. The first is the default, and it copies the object fully by
115 # deep cloning (Object#clone) the non-shareable parts of its structure.
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}"
126 # This will output something like:
128 #     In ractor: 340, 360, 320
129 #     Outside  : 380, 400, 320
131 # Note that the object ids of the array and the non-frozen string inside the array have changed in
132 # the ractor because they are different objects. The second array's element, which is a
133 # shareable frozen string, is the same object.
135 # Deep cloning of objects may be slow, and sometimes impossible. Alternatively, <tt>move: true</tt> may
136 # be used during sending. This will <em>move</em> the unshareable object to the receiving ractor, making it
137 # inaccessible to the sending ractor.
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}"
149 # This will output:
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)
155 # Notice that even +inspect+ (and more basic methods like <tt>__id__</tt>) is inaccessible
156 # on a moved object.
158 # Class and Module objects are shareable so the class/module definitions are shared between ractors.
159 # \Ractor objects are also shareable. All operations on shareable objects are thread-safe, so the thread-safety property
160 # will be kept. We can not define mutable shareable objects in Ruby, but C extensions can introduce them.
162 # It is prohibited to access (get) instance variables of shareable objects in other ractors if the values of the
163 # variables aren't shareable. This can occur because modules/classes are shareable, but they can have
164 # instance variables whose values are not. In non-main ractors, it's also prohibited to set instance
165 # variables on classes/modules (even if the value is shareable).
167 #     class C
168 #       class << self
169 #         attr_accessor :tricky
170 #       end
171 #     end
173 #     C.tricky = "unshareable".dup
175 #     r = Ractor.new(C) do |cls|
176 #       puts "I see #{cls}"
177 #       puts "I can't see #{cls.tricky}"
178 #       cls.tricky = true # doesn't get here, but this would also raise an error
179 #     end
180 #     r.take
181 #     # I see C
182 #     # can not access instance variables of classes/modules from non-main Ractors (RuntimeError)
184 # Ractors can access constants if they are shareable. The main \Ractor is the only one that can
185 # access non-shareable constants.
187 #     GOOD = 'good'.freeze
188 #     BAD = 'bad'.dup
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)
198 #     # Consider the same C class from above
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)
208 # See also the description of <tt># shareable_constant_value</tt> pragma in
209 # {Comments syntax}[rdoc-ref:syntax/comments.rdoc] explanation.
211 # == Ractors vs threads
213 # Each ractor has its own main Thread. New threads can be created from inside ractors
214 # (and, on CRuby, they share the GVL with other threads of this ractor).
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
223 # == Note on code examples
225 # In the examples below, sometimes we use the following method to wait for ractors that
226 # are not currently blocked to finish (or to make progress).
228 #     def wait
229 #       sleep(0.1)
230 #     end
232 # It is **only for demonstration purposes** and shouldn't be used in a real code.
233 # Most of the time, #take is used to wait for ractors to finish.
235 # == Reference
237 # See {Ractor design doc}[rdoc-ref:ractor.md] for more details.
239 class Ractor
240   #
241   #  call-seq:
242   #     Ractor.new(*args, name: nil) {|*args| block } -> ractor
243   #
244   # Create a new \Ractor with args and a block.
245   #
246   # The given block (Proc) will be isolated (can't access any outer variables). +self+
247   # inside the block will refer to the current \Ractor.
248   #
249   #    r = Ractor.new { puts "Hi, I am #{self.inspect}" }
250   #    r.take
251   #    # Prints "Hi, I am #<Ractor:#2 test.rb:1 running>"
252   #
253   # Any +args+ passed are propagated to the block arguments by the same rules as
254   # objects sent via #send/Ractor.receive. If an argument in +args+ is not shareable, it
255   # will be copied (via deep cloning, which might be inefficient).
256   #
257   #    arg = [1, 2, 3]
258   #    puts "Passing: #{arg} (##{arg.object_id})"
259   #    r = Ractor.new(arg) {|received_arg|
260   #      puts "Received: #{received_arg} (##{received_arg.object_id})"
261   #    }
262   #    r.take
263   #    # Prints:
264   #    #   Passing: [1, 2, 3] (#280)
265   #    #   Received: [1, 2, 3] (#300)
266   #
267   # Ractor's +name+ can be set for debugging purposes:
268   #
269   #    r = Ractor.new(name: 'my ractor') {}; r.take
270   #    p r
271   #    #=> #<Ractor:#3 my ractor test.rb:1 terminated>
272   #
273   def self.new(*args, name: nil, &block)
274     b = block # TODO: builtin bug
275     raise ArgumentError, "must be called with a block" unless block
276     if __builtin_cexpr!("RBOOL(ruby_single_main_ractor)")
277       warn("Ractor is experimental, and the behavior may change in future versions of Ruby! " \
278            "Also there are many implementation issues.", uplevel: 0, category: :experimental)
279     end
280     loc = caller_locations(1, 1).first
281     loc = "#{loc.path}:#{loc.lineno}"
282     __builtin_ractor_create(loc, name, args, b)
283   end
285   # Returns the currently executing Ractor.
286   #
287   #   Ractor.current #=> #<Ractor:#1 running>
288   def self.current
289     __builtin_cexpr! %q{
290       rb_ractor_self(rb_ec_ractor_ptr(ec));
291     }
292   end
294   # Returns the number of Ractors currently running or blocking (waiting).
295   #
296   #    Ractor.count                   #=> 1
297   #    r = Ractor.new(name: 'example') { Ractor.yield(1) }
298   #    Ractor.count                   #=> 2 (main + example ractor)
299   #    r.take                         # wait for Ractor.yield(1)
300   #    r.take                         # wait until r will finish
301   #    Ractor.count                   #=> 1
302   def self.count
303     __builtin_cexpr! %q{
304       ULONG2NUM(GET_VM()->ractor.cnt);
305     }
306   end
308   #
309   # call-seq:
310   #    Ractor.select(*ractors, [yield_value:, move: false]) -> [ractor or symbol, obj]
311   #
312   # Wait for any ractor to have something in its outgoing port, read from this ractor, and
313   # then return that ractor and the object received.
314   #
315   #    r1 = Ractor.new {Ractor.yield 'from 1'}
316   #    r2 = Ractor.new {Ractor.yield 'from 2'}
317   #
318   #    r, obj = Ractor.select(r1, r2)
319   #
320   #    puts "received #{obj.inspect} from #{r.inspect}"
321   #    # Prints: received "from 1" from #<Ractor:#2 test.rb:1 running>
322   #    # But could just as well print "from r2" here, either prints could be first.
323   #
324   # If one of the given ractors is the current ractor, and it is selected, +r+ will contain
325   # the +:receive+ symbol instead of the ractor object.
326   #
327   #    r1 = Ractor.new(Ractor.current) do |main|
328   #      main.send 'to main'
329   #      Ractor.yield 'from 1'
330   #    end
331   #    r2 = Ractor.new do
332   #      Ractor.yield 'from 2'
333   #    end
334   #
335   #    r, obj = Ractor.select(r1, r2, Ractor.current)
336   #    puts "received #{obj.inspect} from #{r.inspect}"
337   #    # Could print: received "to main" from :receive
338   #
339   # If +yield_value+ is provided, that value may be yielded if another ractor is calling #take.
340   # In this case, the pair <tt>[:yield, nil]</tt> is returned:
341   #
342   #    r1 = Ractor.new(Ractor.current) do |main|
343   #      puts "Received from main: #{main.take}"
344   #    end
345   #
346   #    puts "Trying to select"
347   #    r, obj = Ractor.select(r1, Ractor.current, yield_value: 123)
348   #    wait
349   #    puts "Received #{obj.inspect} from #{r.inspect}"
350   #
351   # This will print:
352   #
353   #    Trying to select
354   #    Received from main: 123
355   #    Received nil from :yield
356   #
357   # +move+ boolean flag defines whether yielded value will be copied (default) or moved.
358   def self.select(*ractors, yield_value: yield_unspecified = true, move: false)
359     raise ArgumentError, 'specify at least one ractor or `yield_value`' if yield_unspecified && ractors.empty?
361     if ractors.delete Ractor.current
362       do_receive = true
363     else
364       do_receive = false
365     end
367     __builtin_ractor_select_internal ractors, do_receive, !yield_unspecified, yield_value, move
368   end
370   #
371   # call-seq:
372   #    Ractor.receive -> msg
373   #
374   # Receive a message from the incoming port of the current ractor (which was
375   # sent there by #send from another ractor).
376   #
377   #     r = Ractor.new do
378   #       v1 = Ractor.receive
379   #       puts "Received: #{v1}"
380   #     end
381   #     r.send('message1')
382   #     r.take
383   #     # Here will be printed: "Received: message1"
384   #
385   # Alternatively, the private instance method +receive+ may be used:
386   #
387   #     r = Ractor.new do
388   #       v1 = receive
389   #       puts "Received: #{v1}"
390   #     end
391   #     r.send('message1')
392   #     r.take
393   #     # This prints: "Received: message1"
394   #
395   # The method blocks if the queue is empty.
396   #
397   #     r = Ractor.new do
398   #       puts "Before first receive"
399   #       v1 = Ractor.receive
400   #       puts "Received: #{v1}"
401   #       v2 = Ractor.receive
402   #       puts "Received: #{v2}"
403   #     end
404   #     wait
405   #     puts "Still not received"
406   #     r.send('message1')
407   #     wait
408   #     puts "Still received only one"
409   #     r.send('message2')
410   #     r.take
411   #
412   # Output:
413   #
414   #     Before first receive
415   #     Still not received
416   #     Received: message1
417   #     Still received only one
418   #     Received: message2
419   #
420   # If close_incoming was called on the ractor, the method raises Ractor::ClosedError
421   # if there are no more messages in the incoming queue:
422   #
423   #     Ractor.new do
424   #       close_incoming
425   #       receive
426   #     end
427   #     wait
428   #     # in `receive': The incoming port is already closed => #<Ractor:#2 test.rb:1 running> (Ractor::ClosedError)
429   #
430   def self.receive
431     __builtin_cexpr! %q{
432       ractor_receive(ec, rb_ec_ractor_ptr(ec))
433     }
434   end
436   class << self
437     alias recv receive
438   end
440   # same as Ractor.receive
441   private def receive
442     __builtin_cexpr! %q{
443       ractor_receive(ec, rb_ec_ractor_ptr(ec))
444     }
445   end
446   alias recv receive
448   #
449   # call-seq:
450   #    Ractor.receive_if {|msg| block } -> msg
451   #
452   # Receive only a specific message.
453   #
454   # Instead of Ractor.receive, Ractor.receive_if can be given a pattern (or any
455   # filter) in a block and you can choose the messages to accept that are available in
456   # your ractor's incoming queue.
457   #
458   #     r = Ractor.new do
459   #       p Ractor.receive_if{|msg| msg.match?(/foo/)} #=> "foo3"
460   #       p Ractor.receive_if{|msg| msg.match?(/bar/)} #=> "bar1"
461   #       p Ractor.receive_if{|msg| msg.match?(/baz/)} #=> "baz2"
462   #     end
463   #     r << "bar1"
464   #     r << "baz2"
465   #     r << "foo3"
466   #     r.take
467   #
468   # This will output:
469   #
470   #     foo3
471   #     bar1
472   #     baz2
473   #
474   # If the block returns a truthy value, the message is removed from the incoming queue
475   # and returned.
476   # Otherwise, the message remains in the incoming queue and the next messages are checked
477   # by the given block.
478   #
479   # If there are no messages left in the incoming queue, the method will
480   # block until new messages arrive.
481   #
482   # If the block is escaped by break/return/exception/throw, the message is removed from
483   # the incoming queue as if a truthy value had been returned.
484   #
485   #     r = Ractor.new do
486   #       val = Ractor.receive_if{|msg| msg.is_a?(Array)}
487   #       puts "Received successfully: #{val}"
488   #     end
489   #
490   #     r.send(1)
491   #     r.send('test')
492   #     wait
493   #     puts "2 non-matching sent, nothing received"
494   #     r.send([1, 2, 3])
495   #     wait
496   #
497   # Prints:
498   #
499   #     2 non-matching sent, nothing received
500   #     Received successfully: [1, 2, 3]
501   #
502   # Note that you can not call receive/receive_if in the given block recursively.
503   # You should not do any tasks in the block other than message filtration.
504   #
505   #     Ractor.current << true
506   #     Ractor.receive_if{|msg| Ractor.receive}
507   #     #=> `receive': can not call receive/receive_if recursively (Ractor::Error)
508   #
509   def self.receive_if &b
510     Primitive.ractor_receive_if b
511   end
513   # same as Ractor.receive_if
514   private def receive_if &b
515     Primitive.ractor_receive_if b
516   end
518   #
519   # call-seq:
520   #    ractor.send(msg, move: false) -> self
521   #
522   # Send a message to a Ractor's incoming queue to be accepted by Ractor.receive.
523   #
524   #   r = Ractor.new do
525   #     value = Ractor.receive
526   #     puts "Received #{value}"
527   #   end
528   #   r.send 'message'
529   #   # Prints: "Received: message"
530   #
531   # The method is non-blocking (will return immediately even if the ractor is not ready
532   # to receive anything):
533   #
534   #    r = Ractor.new {sleep(5)}
535   #    r.send('test')
536   #    puts "Sent successfully"
537   #    # Prints: "Sent successfully" immediately
538   #
539   # An attempt to send to a ractor which already finished its execution will raise Ractor::ClosedError.
540   #
541   #   r = Ractor.new {}
542   #   r.take
543   #   p r
544   #   # "#<Ractor:#6 (irb):23 terminated>"
545   #   r.send('test')
546   #   # Ractor::ClosedError (The incoming-port is already closed)
547   #
548   # If close_incoming was called on the ractor, the method also raises Ractor::ClosedError.
549   #
550   #    r =  Ractor.new do
551   #      sleep(500)
552   #      receive
553   #    end
554   #    r.close_incoming
555   #    r.send('test')
556   #    # Ractor::ClosedError (The incoming-port is already closed)
557   #    # The error is raised immediately, not when the ractor tries to receive
558   #
559   # If the +obj+ is unshareable, by default it will be copied into the receiving ractor by deep cloning.
560   # If <tt>move: true</tt> is passed, the object is _moved_ into the receiving ractor and becomes
561   # inaccessible to the sender.
562   #
563   #    r = Ractor.new {puts "Received: #{receive}"}
564   #    msg = 'message'
565   #    r.send(msg, move: true)
566   #    r.take
567   #    p msg
568   #
569   # This prints:
570   #
571   #    Received: message
572   #    in `p': undefined method `inspect' for #<Ractor::MovedObject:0x000055c99b9b69b8>
573   #
574   # All references to the object and its parts will become invalid to the sender.
575   #
576   #    r = Ractor.new {puts "Received: #{receive}"}
577   #    s = 'message'
578   #    ary = [s]
579   #    copy = ary.dup
580   #    r.send(ary, move: true)
581   #
582   #    s.inspect
583   #    # Ractor::MovedError (can not send any methods to a moved object)
584   #    ary.class
585   #    # Ractor::MovedError (can not send any methods to a moved object)
586   #    copy.class
587   #    # => Array, it is different object
588   #    copy[0].inspect
589   #    # Ractor::MovedError (can not send any methods to a moved object)
590   #    # ...but its item was still a reference to `s`, which was moved
591   #
592   # If the object is shareable, <tt>move: true</tt> has no effect on it:
593   #
594   #    r = Ractor.new {puts "Received: #{receive}"}
595   #    s = 'message'.freeze
596   #    r.send(s, move: true)
597   #    s.inspect #=> "message", still available
598   #
599   def send(obj, move: false)
600     __builtin_cexpr! %q{
601       ractor_send(ec, RACTOR_PTR(self), obj, move)
602     }
603   end
604   alias << send
606   #
607   #  call-seq:
608   #     Ractor.yield(msg, move: false) -> nil
609   #
610   # Send a message to the current ractor's outgoing port to be accepted by #take.
611   #
612   #    r = Ractor.new {Ractor.yield 'Hello from ractor'}
613   #    puts r.take
614   #    # Prints: "Hello from ractor"
615   #
616   # This method is blocking, and will return only when somebody consumes the
617   # sent message.
618   #
619   #    r = Ractor.new do
620   #      Ractor.yield 'Hello from ractor'
621   #      puts "Ractor: after yield"
622   #    end
623   #    wait
624   #    puts "Still not taken"
625   #    puts r.take
626   #
627   # This will print:
628   #
629   #    Still not taken
630   #    Hello from ractor
631   #    Ractor: after yield
632   #
633   # If the outgoing port was closed with #close_outgoing, the method will raise:
634   #
635   #    r = Ractor.new do
636   #      close_outgoing
637   #      Ractor.yield 'Hello from ractor'
638   #    end
639   #    wait
640   #    # `yield': The outgoing-port is already closed (Ractor::ClosedError)
641   #
642   # The meaning of the +move+ argument is the same as for #send.
643   def self.yield(obj, move: false)
644     __builtin_cexpr! %q{
645       ractor_yield(ec, rb_ec_ractor_ptr(ec), obj, move)
646     }
647   end
649   #
650   #  call-seq:
651   #     ractor.take -> msg
652   #
653   # Get a message from the ractor's outgoing port, which was put there by Ractor.yield or at ractor's
654   # termination.
655   #
656   #   r = Ractor.new do
657   #     Ractor.yield 'explicit yield'
658   #     'last value'
659   #   end
660   #   puts r.take #=> 'explicit yield'
661   #   puts r.take #=> 'last value'
662   #   puts r.take # Ractor::ClosedError (The outgoing-port is already closed)
663   #
664   # The fact that the last value is also sent to the outgoing port means that +take+ can be used
665   # as an analog of Thread#join ("just wait until ractor finishes"). However, it will raise if
666   # somebody has already consumed that message.
667   #
668   # If the outgoing port was closed with #close_outgoing, the method will raise Ractor::ClosedError.
669   #
670   #    r = Ractor.new do
671   #      sleep(500)
672   #      Ractor.yield 'Hello from ractor'
673   #    end
674   #    r.close_outgoing
675   #    r.take
676   #    # Ractor::ClosedError (The outgoing-port is already closed)
677   #    # The error would be raised immediately, not when ractor will try to receive
678   #
679   # If an uncaught exception is raised in the Ractor, it is propagated by take as a
680   # Ractor::RemoteError.
681   #
682   #   r = Ractor.new {raise "Something weird happened"}
683   #
684   #   begin
685   #     r.take
686   #   rescue => e
687   #     p e              #  => #<Ractor::RemoteError: thrown by remote Ractor.>
688   #     p e.ractor == r  # => true
689   #     p e.cause        # => #<RuntimeError: Something weird happened>
690   #   end
691   #
692   # Ractor::ClosedError is a descendant of StopIteration, so the termination of the ractor will break
693   # out of any loops that receive this message without propagating the error:
694   #
695   #     r = Ractor.new do
696   #       3.times {|i| Ractor.yield "message #{i}"}
697   #       "finishing"
698   #     end
699   #
700   #     loop {puts "Received: " + r.take}
701   #     puts "Continue successfully"
702   #
703   # This will print:
704   #
705   #     Received: message 0
706   #     Received: message 1
707   #     Received: message 2
708   #     Received: finishing
709   #     Continue successfully
710   def take
711     __builtin_cexpr! %q{
712       ractor_take(ec, RACTOR_PTR(self))
713     }
714   end
716   def inspect
717     loc  = __builtin_cexpr! %q{ RACTOR_PTR(self)->loc }
718     name = __builtin_cexpr! %q{ RACTOR_PTR(self)->name }
719     id   = __builtin_cexpr! %q{ UINT2NUM(rb_ractor_id(RACTOR_PTR(self))) }
720     status = __builtin_cexpr! %q{
721       rb_str_new2(ractor_status_str(RACTOR_PTR(self)->status_))
722     }
723     "#<Ractor:##{id}#{name ? ' '+name : ''}#{loc ? " " + loc : ''} #{status}>"
724   end
726   alias to_s inspect
728   # The name set in Ractor.new, or +nil+.
729   def name
730     __builtin_cexpr! %q{RACTOR_PTR(self)->name}
731   end
733   class RemoteError
734     attr_reader :ractor
735   end
737   #
738   #  call-seq:
739   #     ractor.close_incoming -> true | false
740   #
741   # Closes the incoming port and returns whether it was already closed. All further attempts
742   # to Ractor.receive in the ractor, and #send to the ractor will fail with Ractor::ClosedError.
743   #
744   #   r = Ractor.new {sleep(500)}
745   #   r.close_incoming  #=> false
746   #   r.close_incoming  #=> true
747   #   r.send('test')
748   #   # Ractor::ClosedError (The incoming-port is already closed)
749   def close_incoming
750     __builtin_cexpr! %q{
751       ractor_close_incoming(ec, RACTOR_PTR(self));
752     }
753   end
755   #
756   # call-seq:
757   #    ractor.close_outgoing -> true | false
758   #
759   # Closes the outgoing port and returns whether it was already closed. All further attempts
760   # to Ractor.yield in the ractor, and #take from the ractor will fail with Ractor::ClosedError.
761   #
762   #   r = Ractor.new {sleep(500)}
763   #   r.close_outgoing  #=> false
764   #   r.close_outgoing  #=> true
765   #   r.take
766   #   # Ractor::ClosedError (The outgoing-port is already closed)
767   def close_outgoing
768     __builtin_cexpr! %q{
769       ractor_close_outgoing(ec, RACTOR_PTR(self));
770     }
771   end
773   #
774   # call-seq:
775   #    Ractor.shareable?(obj) -> true | false
776   #
777   # Checks if the object is shareable by ractors.
778   #
779   #     Ractor.shareable?(1)            #=> true -- numbers and other immutable basic values are frozen
780   #     Ractor.shareable?('foo')        #=> false, unless the string is frozen due to # frozen_string_literal: true
781   #     Ractor.shareable?('foo'.freeze) #=> true
782   #
783   # See also the "Shareable and unshareable objects" section in the \Ractor class docs.
784   def self.shareable? obj
785     __builtin_cexpr! %q{
786       RBOOL(rb_ractor_shareable_p(obj));
787     }
788   end
790   #
791   # call-seq:
792   #    Ractor.make_shareable(obj, copy: false) -> shareable_obj
793   #
794   # Make +obj+ shareable between ractors.
795   #
796   # +obj+ and all the objects it refers to will be frozen, unless they are
797   # already shareable.
798   #
799   # If +copy+ keyword is +true+, it will copy objects before freezing them, and will not
800   # modify +obj+ or its internal objects.
801   #
802   # Note that the specification and implementation of this method are not
803   # mature and may be changed in the future.
804   #
805   #   obj = ['test']
806   #   Ractor.shareable?(obj)     #=> false
807   #   Ractor.make_shareable(obj) #=> ["test"]
808   #   Ractor.shareable?(obj)     #=> true
809   #   obj.frozen?                #=> true
810   #   obj[0].frozen?             #=> true
811   #
812   #   # Copy vs non-copy versions:
813   #   obj1 = ['test']
814   #   obj1s = Ractor.make_shareable(obj1)
815   #   obj1.frozen?                        #=> true
816   #   obj1s.object_id == obj1.object_id   #=> true
817   #   obj2 = ['test']
818   #   obj2s = Ractor.make_shareable(obj2, copy: true)
819   #   obj2.frozen?                        #=> false
820   #   obj2s.frozen?                       #=> true
821   #   obj2s.object_id == obj2.object_id   #=> false
822   #   obj2s[0].object_id == obj2[0].object_id #=> false
823   #
824   # See also the "Shareable and unshareable objects" section in the Ractor class docs.
825   def self.make_shareable obj, copy: false
826     if copy
827       __builtin_cexpr! %q{
828         rb_ractor_make_shareable_copy(obj);
829       }
830     else
831       __builtin_cexpr! %q{
832         rb_ractor_make_shareable(obj);
833       }
834     end
835   end
837   # get a value from ractor-local storage
838   def [](sym)
839     Primitive.ractor_local_value(sym)
840   end
842   # set a value in ractor-local storage
843   def []=(sym, val)
844     Primitive.ractor_local_value_set(sym, val)
845   end
847   # returns main ractor
848   def self.main
849     __builtin_cexpr! %q{
850       rb_ractor_self(GET_VM()->ractor.main_ractor);
851     }
852   end