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