This is a Ruby tree! It shows every object from the Ruby Programming Language in a tree format.

Ractor

        # Ractor < Object

(from ruby core)
---

Ractor is a Actor-model abstraction for Ruby that provides thread-safe
parallel execution.

Ractor.new can make a new Ractor, and it will run in parallel.

    # The simplest ractor
    r = Ractor.new {puts "I am in Ractor!"}
    r.take # wait for it to finish
    # here "I am in Ractor!" would be printed

Ractors do not share usual objects, so the same kinds of thread-safety
concerns such as data-race, race-conditions are not available on
multi-ractor programming.

To achieve this, ractors severely limit object sharing between different
ractors. For example, unlike threads, ractors can't access each other's
objects, nor any objects through variables of the outer scope.

    a = 1
    r = Ractor.new {puts "I am in Ractor! a=#{a}"}
    # fails immediately with
    # 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 ractors are performed in parallel without locking
each other.

Instead of accessing the shared state, the objects should be passed to
and from ractors via sending and receiving objects as messages.

    a = 1
    r = Ractor.new do
      a_in_ractor = receive # receive blocks till somebody will pass message
      puts "I am in Ractor! a=#{a_in_ractor}"
    end
    r.send(a)  # pass it
    r.take
    # here "I am in Ractor! a=1" would be printed

There are two pairs of methods for sending/receiving messages:

*   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);


In addition to that, an argument to Ractor.new would be passed to block
and available there as if received by Ractor.receive, and the last block
value would be sent outside of the ractor as if sent by Ractor.yield.

A little demonstration on a classic ping-pong:

    server = Ractor.new do
      puts "Server starts: #{self.inspect}"
      puts "Server sends: ping"
      Ractor.yield 'ping'                       # The server doesn't know the receiver and sends to whoever interested
      received = Ractor.receive                 # The server doesn't know the sender and receives from whoever sent
      puts "Server received: #{received}"
    end

    client = Ractor.new(server) do |srv|        # The server is sent inside client, and available as srv
      puts "Client starts: #{self.inspect}"
      received = srv.take                       # The Client takes a message specifically from the server
      puts "Client received from " \
           "#{srv.inspect}: #{received}"
      puts "Client sends to " \
           "#{srv.inspect}: pong"
      srv.send 'pong'                           # The client sends a message specifically to the server
    end

    [client, server].each(&:take)               # Wait till they both finish

This will output:

    Server starts: #<Ractor:#2 test.rb:1 running>
    Server sends: ping
    Client starts: #<Ractor:#3 test.rb:8 running>
    Client received from #<Ractor:#2 rac.rb:1 blocking>: ping
    Client sends to #<Ractor:#2 rac.rb:1 blocking>: pong
    Server received: pong

It is said that Ractor receives messages via the *incoming port*, and
sends them to the *outgoing port*. Either one can be disabled with
Ractor#close_incoming and Ractor#close_outgoing respectively. If a
ractor terminated, its ports will be closed automatically.

## Shareable and unshareable objects

When the object is sent to and from the ractor, it is important to
understand whether the object is shareable or unshareable. Most of
objects are unshareable objects.

Shareable objects are basically 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 tries
to make object shareable if it is not.

    Ractor.shareable?(1)            #=> true -- numbers and other immutable basic values are
    Ractor.shareable?('foo')        #=> false, unless the string is frozen due to # freeze_string_literals: true
    Ractor.shareable?('foo'.freeze) #=> true

    ary = ['hello', 'world']
    ary.frozen?                 #=> false
    ary[0].frozen?              #=> false
    Ractor.make_shareable(ary)
    ary.frozen?                 #=> true
    ary[0].frozen?              #=> true
    ary[1].frozen?              #=> true

When a shareable object is sent (via #send or Ractor.yield), no
additional processing happens, and 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 deep cloning of non-shareable parts of its structure.

    data = ['foo', 'bar'.freeze]
    r = Ractor.new do
      data2 = Ractor.receive
      puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}"
    end
    r.send(data)
    r.take
    puts "Outside  : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}"

This will output:

    In ractor: 340, 360, 320
    Outside  : 380, 400, 320

(Note that object id of both array and non-frozen string inside array
have changed inside the ractor, showing it is different objects. But the
second array's element, which is a shareable frozen string, has the same
object_id.)

Deep cloning of the objects may be slow, and sometimes impossible.
Alternatively, `move: true` may be used on sending. This will *move* the
object to the receiving ractor, making it inaccessible for a sending
ractor.

    data = ['foo', 'bar']
    r = Ractor.new do
      data_in_ractor = Ractor.receive
      puts "In ractor: #{data_in_ractor.object_id}, #{data_in_ractor[0].object_id}"
    end
    r.send(data, move: true)
    r.take
    puts "Outside: moved? #{Ractor::MovedObject === data}"
    puts "Outside: #{data.inspect}"

This will output:

    In ractor: 100, 120
    Outside: moved? true
    test.rb:9:in `method_missing': can not send any methods to a moved object (Ractor::MovedError)

Notice that even `inspect` (and more basic methods like `__id__`) is
inaccessible on a moved object.

Besides frozen objects, there are shareable objects. Class and Module
objects are shareable so the Class/Module definitions are shared between
ractors. Ractor objects are also shareable objects. 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.

It is prohibited to access instance variables of mutable shareable
objects (especially Modules and classes) from ractors other than main:

    class C
      class << self
        attr_accessor :tricky
      end
    end

    C.tricky = 'test'

    r = Ractor.new(C) do |cls|
      puts "I see #{cls}"
      puts "I can't see #{cls.tricky}"
    end
    r.take
    # I see C
    # 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 access non-shareable constants.

    GOOD = 'good'.freeze
    BAD = 'bad'

    r = Ractor.new do
      puts "GOOD=#{GOOD}"
      puts "BAD=#{BAD}"
    end
    r.take
    # GOOD=good
    # can not access non-shareable objects in constant Object::BAD by non-main Ractor. (NameError)

    # Consider the same C class from above

    r = Ractor.new do
      puts "I see #{C}"
      puts "I can't see #{C.tricky}"
    end
    r.take
    # I see C
    # can not access instance variables of classes/modules from non-main Ractors (RuntimeError)

See also the description of `# shareable_constant_value` pragma in
[Comments syntax](rdoc-ref:syntax/comments.rdoc) explanation.

## Ractors vs threads

Each ractor creates its own thread. New threads can be created from
inside ractor (and, on CRuby, sharing GVL with other threads of this
ractor).

    r = Ractor.new do
      a = 1
      Thread.new {puts "Thread in ractor: a=#{a}"}.join
    end
    r.take
    # Here "Thread in ractor: a=1" will be printed

## Note on code examples

In examples below, sometimes we use the following method to wait till
ractors that are not currently blocked will finish (or process till next
blocking) method.

    def wait
      sleep(0.1)
    end

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.

## Reference

See [Ractor design doc](rdoc-ref:ractor.md) for more details.
---
# Class methods:

    count
    current
    main
    make_shareable
    new
    receive
    receive_if
    recv
    select
    shareable?
    yield

# Instance methods:

    <<
    []
    []=
    close_incoming
    close_outgoing
    inspect
    name
    receive
    receive_if
    recv
    send
    take
    to_s


      

This is MURDOC! A Ruby documentation browser inspired by Smalltalk-80. It allows you to learn about Ruby by browsing through its class hierarchies, and see any of its methods.