[DOC] Make changes to docs in ractor.rb (#7180)

* Make changes to docs in ractor.rb

Mainly English changes to make things more clear, and to fix minor
non-idiomatic phrases. Also clarified difference between frozen and
shareable objects.

* More minor changes to Ractor docs.
This commit is contained in:
Luke Gruber 2023-01-28 20:01:49 -05:00 committed by GitHub
parent 8250b46794
commit 4ffef59bb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
Notes: git 2023-01-29 01:02:09 +00:00
Merged-By: zzak <zzakscott@gmail.com>

242
ractor.rb
View File

@ -1,51 +1,56 @@
# Ractor is an Actor-model abstraction for Ruby that provides thread-safe parallel execution. # \Ractor is an Actor-model abstraction for Ruby that provides thread-safe parallel execution.
# #
# Ractor.new can make a new Ractor, and it will run in parallel. # Ractor.new makes a new \Ractor, which can run in parallel.
# #
# # The simplest ractor # # The simplest ractor
# r = Ractor.new {puts "I am in Ractor!"} # r = Ractor.new {puts "I am in Ractor!"}
# r.take # wait for it to finish # r.take # wait for it to finish
# # here "I am in Ractor!" would be printed # # Here, "I am in Ractor!" is printed
# #
# Ractors do not share usual objects, so the same kinds of thread-safety concerns such as data-race, # Ractors do not share all objects with each other. There are two main benefits to this: across ractors, thread-safety
# race-conditions are not available on multi-ractor programming. # concerns such as data-races and race-conditions are not possible. The other benefit is parallelism.
# #
# To achieve this, ractors severely limit object sharing between different ractors. # To achieve this, object sharing is limited across ractors.
# For example, unlike threads, ractors can't access each other's objects, nor any objects through # For example, unlike in threads, ractors can't access all the objects available in other ractors. Even objects normally
# variables of the outer scope. # available through variables in the outer scope are prohibited from being used across ractors.
# #
# a = 1 # a = 1
# r = Ractor.new {puts "I am in Ractor! a=#{a}"} # r = Ractor.new {puts "I am in Ractor! a=#{a}"}
# # fails immediately with # # fails immediately with
# # ArgumentError (can not isolate a Proc because it accesses outer variables (a).) # # ArgumentError (can not isolate a Proc because it accesses outer variables (a).)
# #
# On CRuby (the default implementation), Global Virtual Machine Lock (GVL) is held per ractor, so # The object must be explicity shared:
# ractors are performed in parallel without locking each other. # a = 1
# r = Ractor.new(a) { |a1| puts "I am in Ractor! a=#{a1}"}
# #
# Instead of accessing the shared state, the objects should be passed to and from ractors via # On CRuby (the default implementation), Global Virtual Machine Lock (GVL) is held per ractor, so
# sending and receiving objects as messages. # ractors can perform in parallel without locking each other. This is unlike the situation with threads
# on CRuby.
#
# Instead of accessing shared state, objects should be passed to and from ractors by
# sending and receiving them as messages.
# #
# a = 1 # a = 1
# r = Ractor.new do # r = Ractor.new do
# a_in_ractor = receive # receive blocks till somebody will pass message # a_in_ractor = receive # receive blocks until somebody passes a message
# puts "I am in Ractor! a=#{a_in_ractor}" # puts "I am in Ractor! a=#{a_in_ractor}"
# end # end
# r.send(a) # pass it # r.send(a) # pass it
# r.take # r.take
# # here "I am in Ractor! a=1" would be printed # # Here, "I am in Ractor! a=1" is printed
# #
# There are two pairs of methods for sending/receiving messages: # There are two pairs of methods for sending/receiving messages:
# #
# * Ractor#send and Ractor.receive for when the _sender_ knows the receiver (push); # * Ractor#send and Ractor.receive for when the _sender_ knows the receiver (push);
# * Ractor.yield and Ractor#take for when the _receiver_ knows the sender (pull); # * Ractor.yield and Ractor#take for when the _receiver_ knows the sender (pull);
# #
# In addition to that, an argument to Ractor.new would be passed to block and available there # In addition to that, any arguments passed to Ractor.new are passed to the block and available there
# as if received by Ractor.receive, and the last block value would be sent outside of the # as if received by Ractor.receive, and the last block value is sent outside of the
# ractor as if sent by Ractor.yield. # ractor as if sent by Ractor.yield.
# #
# A little demonstration on a classic ping-pong: # A little demonstration of a classic ping-pong:
# #
# server = Ractor.new do # server = Ractor.new(name: "server") do
# puts "Server starts: #{self.inspect}" # puts "Server starts: #{self.inspect}"
# puts "Server sends: ping" # puts "Server sends: ping"
# Ractor.yield 'ping' # The server doesn't know the receiver and sends to whoever interested # Ractor.yield 'ping' # The server doesn't know the receiver and sends to whoever interested
@ -53,44 +58,48 @@
# puts "Server received: #{received}" # puts "Server received: #{received}"
# end # end
# #
# client = Ractor.new(server) do |srv| # The server is sent inside client, and available as srv # client = Ractor.new(server) do |srv| # The server is sent to the client, and available as srv
# puts "Client starts: #{self.inspect}" # puts "Client starts: #{self.inspect}"
# received = srv.take # The Client takes a message specifically from the server # received = srv.take # The client takes a message from the server
# puts "Client received from " \ # puts "Client received from " \
# "#{srv.inspect}: #{received}" # "#{srv.inspect}: #{received}"
# puts "Client sends to " \ # puts "Client sends to " \
# "#{srv.inspect}: pong" # "#{srv.inspect}: pong"
# srv.send 'pong' # The client sends a message specifically to the server # srv.send 'pong' # The client sends a message to the server
# end # end
# #
# [client, server].each(&:take) # Wait till they both finish # [client, server].each(&:take) # Wait until they both finish
# #
# This will output: # This will output something like:
# #
# Server starts: #<Ractor:#2 test.rb:1 running> # Server starts: #<Ractor:#2 server test.rb:1 running>
# Server sends: ping # Server sends: ping
# Client starts: #<Ractor:#3 test.rb:8 running> # Client starts: #<Ractor:#3 test.rb:8 running>
# Client received from #<Ractor:#2 rac.rb:1 blocking>: ping # Client received from #<Ractor:#2 server test.rb:1 blocking>: ping
# Client sends to #<Ractor:#2 rac.rb:1 blocking>: pong # Client sends to #<Ractor:#2 server test.rb:1 blocking>: pong
# Server received: pong # Server received: pong
# #
# It is said that Ractor receives messages via the <em>incoming port</em>, and sends them # Ractors receive their messages via the <em>incoming port</em>, and send them
# to the <em>outgoing port</em>. Either one can be disabled with Ractor#close_incoming and # to the <em>outgoing port</em>. Either one can be disabled with Ractor#close_incoming and
# Ractor#close_outgoing respectively. If a ractor terminated, its ports will be closed # Ractor#close_outgoing, respectively. When a ractor terminates, its ports are closed
# automatically. # automatically.
# #
# == Shareable and unshareable objects # == Shareable and unshareable objects
# #
# When the object is sent to and from the ractor, it is important to understand whether the # When an object is sent to and from a ractor, it's important to understand whether the
# object is shareable or unshareable. Most of objects are unshareable objects. # object is shareable or unshareable. Most Ruby objects are unshareable objects. Even
# frozen objects can be unshareable if they contain (through their instance variables) unfrozen
# objects.
# #
# Shareable objects are basically those which can be used by several threads without compromising # Shareable objects are those which can be used by several threads without compromising
# thread-safety; e.g. immutable ones. Ractor.shareable? allows to check this, and Ractor.make_shareable # thread-safety, for example numbers, +true+ and +false+. Ractor.shareable? allows you to check this,
# tries to make object shareable if it is not. # and Ractor.make_shareable tries to make the object shareable if it's not already, and gives an error
# if it can't do it.
# #
# Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are shareable
# Ractor.shareable?('foo') #=> false, unless the string is frozen due to # freeze_string_literals: true # Ractor.shareable?('foo') #=> false, unless the string is frozen due to # frozen_string_literal: true
# Ractor.shareable?('foo'.freeze) #=> true # Ractor.shareable?('foo'.freeze) #=> true
# Ractor.shareable?([Object.new].freeze) #=> false, inner object is unfrozen
# #
# ary = ['hello', 'world'] # ary = ['hello', 'world']
# ary.frozen? #=> false # ary.frozen? #=> false
@ -100,10 +109,10 @@
# ary[0].frozen? #=> true # ary[0].frozen? #=> true
# ary[1].frozen? #=> true # ary[1].frozen? #=> true
# #
# When a shareable object is sent (via #send or Ractor.yield), no additional processing happens, # When a shareable object is sent (via #send or Ractor.yield), no additional processing occurs
# and it just becomes usable by both ractors. When an unshareable object is sent, it can be # on it. It just becomes usable by both ractors. When an unshareable object is sent, it can be
# either _copied_ or _moved_. The first is the default, and it makes the object's full copy by # either _copied_ or _moved_. The first is the default, and it copies the object fully by
# deep cloning of non-shareable parts of its structure. # deep cloning (Object#clone) the non-shareable parts of its structure.
# #
# data = ['foo', 'bar'.freeze] # data = ['foo', 'bar'.freeze]
# r = Ractor.new do # r = Ractor.new do
@ -114,18 +123,18 @@
# r.take # r.take
# puts "Outside : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}" # puts "Outside : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}"
# #
# This will output: # This will output something like:
# #
# In ractor: 340, 360, 320 # In ractor: 340, 360, 320
# Outside : 380, 400, 320 # Outside : 380, 400, 320
# #
# (Note that object id of both array and non-frozen string inside array have changed inside # Note that the object ids of the array and the non-frozen string inside the array have changed in
# the ractor, showing it is different objects. But the second array's element, which is a # the ractor because they are different objects. The second array's element, which is a
# shareable frozen string, has the same object_id.) # shareable frozen string, is the same object.
# #
# Deep cloning of the objects may be slow, and sometimes impossible. Alternatively, # Deep cloning of objects may be slow, and sometimes impossible. Alternatively, <tt>move: true</tt> may
# <tt>move: true</tt> may be used on sending. This will <em>move</em> the object to the # be used during sending. This will <em>move</em> the unshareable object to the receiving ractor, making it
# receiving ractor, making it inaccessible for a sending ractor. # inaccessible to the sending ractor.
# #
# data = ['foo', 'bar'] # data = ['foo', 'bar']
# r = Ractor.new do # r = Ractor.new do
@ -146,13 +155,14 @@
# Notice that even +inspect+ (and more basic methods like <tt>__id__</tt>) is inaccessible # Notice that even +inspect+ (and more basic methods like <tt>__id__</tt>) is inaccessible
# on a moved object. # on a moved object.
# #
# Besides frozen objects, there are shareable objects. Class and Module objects are shareable so # Class and Module objects are shareable so the class/module definitions are shared between ractors.
# the Class/Module definitions are shared between ractors. Ractor objects are also shareable objects. # \Ractor objects are also shareable. All operations on shareable objects are thread-safe, so the thread-safety property
# All operations for the shareable mutable objects are thread-safe, so the thread-safety property
# will be kept. We can not define mutable shareable objects in Ruby, but C extensions can introduce them. # will be kept. We can not define mutable shareable objects in Ruby, but C extensions can introduce them.
# #
# It is prohibited to access instance variables of mutable shareable objects (especially Modules and classes) # It is prohibited to access (get) instance variables of shareable objects in other ractors if the values of the
# from ractors other than main: # variables aren't shareable. This can occur because modules/classes are shareable, but they can have
# instance variables whose values are not. In non-main ractors, it's also prohibited to set instance
# variables on classes/modules (even if the value is shareable).
# #
# class C # class C
# class << self # class << self
@ -160,21 +170,22 @@
# end # end
# end # end
# #
# C.tricky = 'test' # C.tricky = "unshareable".dup
# #
# r = Ractor.new(C) do |cls| # r = Ractor.new(C) do |cls|
# puts "I see #{cls}" # puts "I see #{cls}"
# puts "I can't see #{cls.tricky}" # puts "I can't see #{cls.tricky}"
# cls.tricky = true # doesn't get here, but this would also raise an error
# end # end
# r.take # r.take
# # I see C # # I see C
# # can not access instance variables of classes/modules from non-main Ractors (RuntimeError) # # can not access instance variables of classes/modules from non-main Ractors (RuntimeError)
# #
# Ractors can access constants if they are shareable. The main Ractor is the only one that can # Ractors can access constants if they are shareable. The main \Ractor is the only one that can
# access non-shareable constants. # access non-shareable constants.
# #
# GOOD = 'good'.freeze # GOOD = 'good'.freeze
# BAD = 'bad' # BAD = 'bad'.dup
# #
# r = Ractor.new do # r = Ractor.new do
# puts "GOOD=#{GOOD}" # puts "GOOD=#{GOOD}"
@ -199,8 +210,8 @@
# #
# == Ractors vs threads # == Ractors vs threads
# #
# Each ractor creates its own thread. New threads can be created from inside ractor # Each ractor has its own main Thread. New threads can be created from inside ractors
# (and, on CRuby, sharing GVL with other threads of this ractor). # (and, on CRuby, they share the GVL with other threads of this ractor).
# #
# r = Ractor.new do # r = Ractor.new do
# a = 1 # a = 1
@ -211,15 +222,15 @@
# #
# == Note on code examples # == Note on code examples
# #
# In examples below, sometimes we use the following method to wait till ractors that # In the examples below, sometimes we use the following method to wait for ractors that
# are not currently blocked will finish (or process till next blocking) method. # are not currently blocked to finish (or to make progress).
# #
# def wait # def wait
# sleep(0.1) # sleep(0.1)
# end # end
# #
# It is **only for demonstration purposes** and shouldn't be used in a real code. # It is **only for demonstration purposes** and shouldn't be used in a real code.
# Most of the times, just #take is used to wait till ractor will finish. # Most of the time, #take is used to wait for ractors to finish.
# #
# == Reference # == Reference
# #
@ -230,17 +241,17 @@ class Ractor
# call-seq: # call-seq:
# Ractor.new(*args, name: nil) {|*args| block } -> ractor # Ractor.new(*args, name: nil) {|*args| block } -> ractor
# #
# Create a new Ractor with args and a block. # Create a new \Ractor with args and a block.
# #
# A block (Proc) will be isolated (can't access to outer variables). +self+ # The given block (Proc) will be isolated (can't access any outer variables). +self+
# inside the block will refer to the current Ractor. # inside the block will refer to the current \Ractor.
# #
# r = Ractor.new { puts "Hi, I am #{self.inspect}" } # r = Ractor.new { puts "Hi, I am #{self.inspect}" }
# r.take # r.take
# # Prints "Hi, I am #<Ractor:#2 test.rb:1 running>" # # Prints "Hi, I am #<Ractor:#2 test.rb:1 running>"
# #
# +args+ passed to the method would be propagated to block args by the same rules as # Any +args+ passed are propagated to the block arguments by the same rules as
# objects passed through #send/Ractor.receive: if +args+ are not shareable, they # objects sent via #send/Ractor.receive. If an argument in +args+ is not shareable, it
# will be copied (via deep cloning, which might be inefficient). # will be copied (via deep cloning, which might be inefficient).
# #
# arg = [1, 2, 3] # arg = [1, 2, 3]
@ -255,7 +266,7 @@ class Ractor
# #
# Ractor's +name+ can be set for debugging purposes: # Ractor's +name+ can be set for debugging purposes:
# #
# r = Ractor.new(name: 'my ractor') {} # r = Ractor.new(name: 'my ractor') {}; r.take
# p r # p r
# #=> #<Ractor:#3 my ractor test.rb:1 terminated> # #=> #<Ractor:#3 my ractor test.rb:1 terminated>
# #
@ -280,13 +291,13 @@ class Ractor
} }
end end
# Returns total count of Ractors currently running. # Returns the number of Ractors currently running or blocking (waiting).
# #
# Ractor.count #=> 1 # Ractor.count #=> 1
# r = Ractor.new(name: 'example') { Ractor.yield(1) } # r = Ractor.new(name: 'example') { Ractor.yield(1) }
# Ractor.count #=> 2 (main + example ractor) # Ractor.count #=> 2 (main + example ractor)
# r.take # wait for Ractor.yield(1) # r.take # wait for Ractor.yield(1)
# r.take # wait till r will finish # r.take # wait until r will finish
# Ractor.count #=> 1 # Ractor.count #=> 1
def self.count def self.count
__builtin_cexpr! %q{ __builtin_cexpr! %q{
@ -298,8 +309,8 @@ class Ractor
# call-seq: # call-seq:
# Ractor.select(*ractors, [yield_value:, move: false]) -> [ractor or symbol, obj] # Ractor.select(*ractors, [yield_value:, move: false]) -> [ractor or symbol, obj]
# #
# Waits for the first ractor to have something in its outgoing port, reads from this ractor, and # Wait for any ractor to have something in its outgoing port, read from this ractor, and
# returns that ractor and the object received. # then return that ractor and the object received.
# #
# r1 = Ractor.new {Ractor.yield 'from 1'} # r1 = Ractor.new {Ractor.yield 'from 1'}
# r2 = Ractor.new {Ractor.yield 'from 2'} # r2 = Ractor.new {Ractor.yield 'from 2'}
@ -308,9 +319,10 @@ class Ractor
# #
# puts "received #{obj.inspect} from #{r.inspect}" # puts "received #{obj.inspect} from #{r.inspect}"
# # Prints: received "from 1" from #<Ractor:#2 test.rb:1 running> # # Prints: received "from 1" from #<Ractor:#2 test.rb:1 running>
# # But could just as well print "from r2" here, either prints could be first.
# #
# If one of the given ractors is the current ractor, and it would be selected, +r+ will contain # If one of the given ractors is the current ractor, and it is selected, +r+ will contain
# +:receive+ symbol instead of the ractor object. # the +:receive+ symbol instead of the ractor object.
# #
# r1 = Ractor.new(Ractor.current) do |main| # r1 = Ractor.new(Ractor.current) do |main|
# main.send 'to main' # main.send 'to main'
@ -322,10 +334,10 @@ class Ractor
# #
# r, obj = Ractor.select(r1, r2, Ractor.current) # r, obj = Ractor.select(r1, r2, Ractor.current)
# puts "received #{obj.inspect} from #{r.inspect}" # puts "received #{obj.inspect} from #{r.inspect}"
# # Prints: received "to main" from :receive # # Could print: received "to main" from :receive
# #
# If +yield_value+ is provided, that value may be yielded if another Ractor is calling #take. # If +yield_value+ is provided, that value may be yielded if another ractor is calling #take.
# In this case, the pair <tt>[:yield, nil]</tt> would be returned: # In this case, the pair <tt>[:yield, nil]</tt> is returned:
# #
# r1 = Ractor.new(Ractor.current) do |main| # r1 = Ractor.new(Ractor.current) do |main|
# puts "Received from main: #{main.take}" # puts "Received from main: #{main.take}"
@ -342,7 +354,7 @@ class Ractor
# Received from main: 123 # Received from main: 123
# Received nil from :yield # Received nil from :yield
# #
# +move+ boolean flag defines whether yielded value should be copied (default) or moved. # +move+ boolean flag defines whether yielded value will be copied (default) or moved.
def self.select(*ractors, yield_value: yield_unspecified = true, move: false) def self.select(*ractors, yield_value: yield_unspecified = true, move: false)
raise ArgumentError, 'specify at least one ractor or `yield_value`' if yield_unspecified && ractors.empty? raise ArgumentError, 'specify at least one ractor or `yield_value`' if yield_unspecified && ractors.empty?
@ -360,8 +372,8 @@ class Ractor
# call-seq: # call-seq:
# Ractor.receive -> msg # Ractor.receive -> msg
# #
# Receive an incoming message from the current Ractor's incoming port's queue, which was # Receive a message from the incoming port of the current ractor (which was
# sent there by #send. # sent there by #send from another ractor).
# #
# r = Ractor.new do # r = Ractor.new do
# v1 = Ractor.receive # v1 = Ractor.receive
@ -371,7 +383,7 @@ class Ractor
# r.take # r.take
# # Here will be printed: "Received: message1" # # Here will be printed: "Received: message1"
# #
# Alternatively, private instance method +receive+ may be used: # Alternatively, the private instance method +receive+ may be used:
# #
# r = Ractor.new do # r = Ractor.new do
# v1 = receive # v1 = receive
@ -379,7 +391,7 @@ class Ractor
# end # end
# r.send('message1') # r.send('message1')
# r.take # r.take
# # Here will be printed: "Received: message1" # # This prints: "Received: message1"
# #
# The method blocks if the queue is empty. # The method blocks if the queue is empty.
# #
@ -407,7 +419,7 @@ class Ractor
# Received: message2 # Received: message2
# #
# If close_incoming was called on the ractor, the method raises Ractor::ClosedError # If close_incoming was called on the ractor, the method raises Ractor::ClosedError
# if there are no more messages in incoming queue: # if there are no more messages in the incoming queue:
# #
# Ractor.new do # Ractor.new do
# close_incoming # close_incoming
@ -440,8 +452,9 @@ class Ractor
# #
# Receive only a specific message. # Receive only a specific message.
# #
# Instead of Ractor.receive, Ractor.receive_if can provide a pattern # Instead of Ractor.receive, Ractor.receive_if can be given a pattern (or any
# by a block and you can choose the receiving message. # filter) in a block and you can choose the messages to accept that are available in
# your ractor's incoming queue.
# #
# r = Ractor.new do # r = Ractor.new do
# p Ractor.receive_if{|msg| msg.match?(/foo/)} #=> "foo3" # p Ractor.receive_if{|msg| msg.match?(/foo/)} #=> "foo3"
@ -459,10 +472,10 @@ class Ractor
# bar1 # bar1
# baz2 # baz2
# #
# If the block returns a truthy value, the message will be removed from the incoming queue # If the block returns a truthy value, the message is removed from the incoming queue
# and returned. # and returned.
# Otherwise, the message remains in the incoming queue and the following received # Otherwise, the message remains in the incoming queue and the next messages are checked
# messages are checked by the given block. # by the given block.
# #
# If there are no messages left in the incoming queue, the method will # If there are no messages left in the incoming queue, the method will
# block until new messages arrive. # block until new messages arrive.
@ -488,7 +501,7 @@ class Ractor
# Received successfully: [1, 2, 3] # Received successfully: [1, 2, 3]
# #
# Note that you can not call receive/receive_if in the given block recursively. # Note that you can not call receive/receive_if in the given block recursively.
# It means that you should not do any tasks in the block. # You should not do any tasks in the block other than message filtration.
# #
# Ractor.current << true # Ractor.current << true
# Ractor.receive_if{|msg| Ractor.receive} # Ractor.receive_if{|msg| Ractor.receive}
@ -498,6 +511,7 @@ class Ractor
Primitive.ractor_receive_if b Primitive.ractor_receive_if b
end end
# same as Ractor.receive_if
private def receive_if &b private def receive_if &b
Primitive.ractor_receive_if b Primitive.ractor_receive_if b
end end
@ -506,7 +520,7 @@ class Ractor
# call-seq: # call-seq:
# ractor.send(msg, move: false) -> self # ractor.send(msg, move: false) -> self
# #
# Send a message to a Ractor's incoming queue to be consumed by Ractor.receive. # Send a message to a Ractor's incoming queue to be accepted by Ractor.receive.
# #
# r = Ractor.new do # r = Ractor.new do
# value = Ractor.receive # value = Ractor.receive
@ -523,7 +537,7 @@ class Ractor
# puts "Sent successfully" # puts "Sent successfully"
# # Prints: "Sent successfully" immediately # # Prints: "Sent successfully" immediately
# #
# Attempt to send to ractor which already finished its execution will raise Ractor::ClosedError. # An attempt to send to a ractor which already finished its execution will raise Ractor::ClosedError.
# #
# r = Ractor.new {} # r = Ractor.new {}
# r.take # r.take
@ -541,11 +555,11 @@ class Ractor
# r.close_incoming # r.close_incoming
# r.send('test') # r.send('test')
# # Ractor::ClosedError (The incoming-port is already closed) # # Ractor::ClosedError (The incoming-port is already closed)
# # The error would be raised immediately, not when ractor will try to receive # # The error is raised immediately, not when the ractor tries to receive
# #
# If the +obj+ is unshareable, by default it would be copied into ractor by deep cloning. # If the +obj+ is unshareable, by default it will be copied into the receiving ractor by deep cloning.
# If the <tt>move: true</tt> is passed, object is _moved_ into ractor and becomes # If <tt>move: true</tt> is passed, the object is _moved_ into the receiving ractor and becomes
# inaccessible to sender. # inaccessible to the sender.
# #
# r = Ractor.new {puts "Received: #{receive}"} # r = Ractor.new {puts "Received: #{receive}"}
# msg = 'message' # msg = 'message'
@ -558,7 +572,7 @@ class Ractor
# Received: message # Received: message
# in `p': undefined method `inspect' for #<Ractor::MovedObject:0x000055c99b9b69b8> # in `p': undefined method `inspect' for #<Ractor::MovedObject:0x000055c99b9b69b8>
# #
# All references to the object and its parts will become invalid in sender. # All references to the object and its parts will become invalid to the sender.
# #
# r = Ractor.new {puts "Received: #{receive}"} # r = Ractor.new {puts "Received: #{receive}"}
# s = 'message' # s = 'message'
@ -576,7 +590,7 @@ class Ractor
# # Ractor::MovedError (can not send any methods to a moved object) # # Ractor::MovedError (can not send any methods to a moved object)
# # ...but its item was still a reference to `s`, which was moved # # ...but its item was still a reference to `s`, which was moved
# #
# If the object was shareable, <tt>move: true</tt> has no effect on it: # If the object is shareable, <tt>move: true</tt> has no effect on it:
# #
# r = Ractor.new {puts "Received: #{receive}"} # r = Ractor.new {puts "Received: #{receive}"}
# s = 'message'.freeze # s = 'message'.freeze
@ -594,13 +608,13 @@ class Ractor
# call-seq: # call-seq:
# Ractor.yield(msg, move: false) -> nil # Ractor.yield(msg, move: false) -> nil
# #
# Send a message to the current ractor's outgoing port to be consumed by #take. # Send a message to the current ractor's outgoing port to be accepted by #take.
# #
# r = Ractor.new {Ractor.yield 'Hello from ractor'} # r = Ractor.new {Ractor.yield 'Hello from ractor'}
# puts r.take # puts r.take
# # Prints: "Hello from ractor" # # Prints: "Hello from ractor"
# #
# The method is blocking, and will return only when somebody consumes the # This method is blocking, and will return only when somebody consumes the
# sent message. # sent message.
# #
# r = Ractor.new do # r = Ractor.new do
@ -626,7 +640,7 @@ class Ractor
# wait # wait
# # `yield': The outgoing-port is already closed (Ractor::ClosedError) # # `yield': The outgoing-port is already closed (Ractor::ClosedError)
# #
# The meaning of +move+ argument is the same as for #send. # The meaning of the +move+ argument is the same as for #send.
def self.yield(obj, move: false) def self.yield(obj, move: false)
__builtin_cexpr! %q{ __builtin_cexpr! %q{
ractor_yield(ec, rb_ec_ractor_ptr(ec), obj, move) ractor_yield(ec, rb_ec_ractor_ptr(ec), obj, move)
@ -637,8 +651,8 @@ class Ractor
# call-seq: # call-seq:
# ractor.take -> msg # ractor.take -> msg
# #
# Take a message from ractor's outgoing port, which was put there by Ractor.yield or at ractor's # Get a message from the ractor's outgoing port, which was put there by Ractor.yield or at ractor's
# finalization. # termination.
# #
# r = Ractor.new do # r = Ractor.new do
# Ractor.yield 'explicit yield' # Ractor.yield 'explicit yield'
@ -648,9 +662,9 @@ class Ractor
# puts r.take #=> 'last value' # puts r.take #=> 'last value'
# puts r.take # Ractor::ClosedError (The outgoing-port is already closed) # puts r.take # Ractor::ClosedError (The outgoing-port is already closed)
# #
# The fact that the last value is also put to outgoing port means that +take+ can be used # The fact that the last value is also sent to the outgoing port means that +take+ can be used
# as some analog of Thread#join ("just wait till ractor finishes"), but don't forget it # as an analog of Thread#join ("just wait until ractor finishes"). However, it will raise if
# will raise if somebody had already consumed everything ractor have produced. # somebody has already consumed that message.
# #
# If the outgoing port was closed with #close_outgoing, the method will raise Ractor::ClosedError. # If the outgoing port was closed with #close_outgoing, the method will raise Ractor::ClosedError.
# #
@ -663,7 +677,7 @@ class Ractor
# # Ractor::ClosedError (The outgoing-port is already closed) # # Ractor::ClosedError (The outgoing-port is already closed)
# # The error would be raised immediately, not when ractor will try to receive # # The error would be raised immediately, not when ractor will try to receive
# #
# If an uncaught exception is raised in the Ractor, it is propagated on take as a # If an uncaught exception is raised in the Ractor, it is propagated by take as a
# Ractor::RemoteError. # Ractor::RemoteError.
# #
# r = Ractor.new {raise "Something weird happened"} # r = Ractor.new {raise "Something weird happened"}
@ -676,8 +690,8 @@ class Ractor
# p e.cause # => #<RuntimeError: Something weird happened> # p e.cause # => #<RuntimeError: Something weird happened>
# end # end
# #
# Ractor::ClosedError is a descendant of StopIteration, so the closing of the ractor will break # Ractor::ClosedError is a descendant of StopIteration, so the termination of the ractor will break
# the loops without propagating the error: # out of any loops that receive this message without propagating the error:
# #
# r = Ractor.new do # r = Ractor.new do
# 3.times {|i| Ractor.yield "message #{i}"} # 3.times {|i| Ractor.yield "message #{i}"}
@ -725,9 +739,8 @@ class Ractor
# call-seq: # call-seq:
# ractor.close_incoming -> true | false # ractor.close_incoming -> true | false
# #
# Closes the incoming port and returns its previous state. # Closes the incoming port and returns whether it was already closed. All further attempts
# All further attempts to Ractor.receive in the ractor, and #send to the ractor # to Ractor.receive in the ractor, and #send to the ractor will fail with Ractor::ClosedError.
# will fail with Ractor::ClosedError.
# #
# r = Ractor.new {sleep(500)} # r = Ractor.new {sleep(500)}
# r.close_incoming #=> false # r.close_incoming #=> false
@ -744,9 +757,8 @@ class Ractor
# call-seq: # call-seq:
# ractor.close_outgoing -> true | false # ractor.close_outgoing -> true | false
# #
# Closes the outgoing port and returns its previous state. # Closes the outgoing port and returns whether it was already closed. All further attempts
# All further attempts to Ractor.yield in the ractor, and #take from the ractor # to Ractor.yield in the ractor, and #take from the ractor will fail with Ractor::ClosedError.
# will fail with Ractor::ClosedError.
# #
# r = Ractor.new {sleep(500)} # r = Ractor.new {sleep(500)}
# r.close_outgoing #=> false # r.close_outgoing #=> false
@ -766,10 +778,10 @@ class Ractor
# Checks if the object is shareable by ractors. # Checks if the object is shareable by ractors.
# #
# Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are frozen # Ractor.shareable?(1) #=> true -- numbers and other immutable basic values are frozen
# Ractor.shareable?('foo') #=> false, unless the string is frozen due to # freeze_string_literals: true # Ractor.shareable?('foo') #=> false, unless the string is frozen due to # frozen_string_literal: true
# Ractor.shareable?('foo'.freeze) #=> true # Ractor.shareable?('foo'.freeze) #=> true
# #
# See also the "Shareable and unshareable objects" section in the Ractor class docs. # See also the "Shareable and unshareable objects" section in the \Ractor class docs.
def self.shareable? obj def self.shareable? obj
__builtin_cexpr! %q{ __builtin_cexpr! %q{
RBOOL(rb_ractor_shareable_p(obj)); RBOOL(rb_ractor_shareable_p(obj));
@ -785,8 +797,8 @@ class Ractor
# +obj+ and all the objects it refers to will be frozen, unless they are # +obj+ and all the objects it refers to will be frozen, unless they are
# already shareable. # already shareable.
# #
# If +copy+ keyword is +true+, the method will copy objects before freezing them # If +copy+ keyword is +true+, it will copy objects before freezing them, and will not
# This is safer option but it can take be slower. # modify +obj+ or its internal objects.
# #
# Note that the specification and implementation of this method are not # Note that the specification and implementation of this method are not
# mature and may be changed in the future. # mature and may be changed in the future.