2

I'm trying to understand the behavior of the move flag in Ruby's Ractor.select method. method signature: https://ruby-doc.org/3.2.1/Ractor.html#method-c-current

The description of the function's move flag in the documentation describes the flag in the following way:

move boolean flag defines whether yielded value should be copied (default) or moved.

This statement makes sense in a language with the ownership model in place like rust. But I'm trying to understand what it means for ruby

I ran a piece of code to experiment with the effect of this flag.

should_move = true

puts "MOVE: #{should_move}"
r1 = Ractor.new do
  data = [{name: "tom"}, {name: "dick"}, {name: "harry"}]
  puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
  Ractor.yield(data)
  sleep 2
  puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
end

ractor, value = Ractor.select(r1, move: should_move)


puts "[OUTSIDE RACTOR] value is: #{value} and value[0] object_id is: #{value[0].object_id}"
p ractor

sleep 3

in the above code, the output was the same for both should_move = true && should_move = false.

MOVE: true
main.rb:4: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
[INSIDE RACTOR (before move)] data value is [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and data object id is :60
[OUTSIDE RACTOR (after move)] value is: [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and value[0] object_id is: 80
#<Ractor:#2 main.rb:4 blocking>
[INSIDE RACTOR (after move)] data value is [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and data object id is :60

and then I tried running the same thing, but this time, I set data variable inside the ractor to an integer value as follows:

should_move = true

puts "MOVE: #{should_move}"
r1 = Ractor.new do
  data = 12
  puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data.object_id}"
  Ractor.yield(data)
  sleep 2
  puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data.object_id}"
end

ractor, value = Ractor.select(r1, move: should_move)


puts "[OUTSIDE RACTOR] value is: #{value} and value object_id is: #{value.object_id}"
p ractor

sleep 3

this time, the output in this case was as follows:

MOVE: false
main.rb:4: warning: Ractor is experimental, and the behavior may change in future versions of Ruby! Also there are many implementation issues.
[INSIDE RACTOR (before move)] data value is 12 and data object id is :25
[OUTSIDE RACTOR (after move)] value is: 12 and value[0] object_id is: 25
#<Ractor:#2 main.rb:4 blocking>
[INSIDE RACTOR (after move)] data value is 12 and data object id is :25

the move flag state did not affect the output in this case as well.

sreedev
  • 51
  • 1
  • 5
  • It is kind of hidden at the bottom of the most recent [docs](https://ruby-doc.org/core-3.1.2/Ractor.html#method-c-select): *"`move` boolean flag defines whether yielded value should be copied (default) or moved"*. It is also mentioned in the [Shareable and unshareable objects](https://ruby-doc.org/core-3.1.2/Ractor.html#class-Ractor-label-Shareable+and+unshareable+objects) section: *"Alternatively, `move: true` may be used on sending. This will move the object to the receiving ractor, making it inaccessible for a sending ractor."* – engineersmnky Aug 21 '23 at 15:54
  • @engineersmnky Thank you for your response. I tried modifying the "moved" data from inside the ractor with (move: true) & (move: false). in both cases, I noticed that the variable that was moved out of the ractor was a clone. the move flag did not have an effect on how the value was moved out of the function (copy/move). The documentation feels a little inconsistent. – sreedev Aug 22 '23 at 00:33

1 Answers1

1

I think you are missing what move does and what it impacts in the context of Ractor::select

move will make the sent object inaccessible to the sender, you can see this by modifying you example slightly.

r1 = Ractor.new do
  data = [{name: "tom"}, {name: "dick"}, {name: "harry"}]
  puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
  Ractor.yield(data, move: true)
  begin 
    puts "[INSIDE RACTOR] data value is #{data} and data object id is :#{data[0].object_id}"
  rescue Ractor::MovedError => e 
    puts
    puts "data is no longer accessible because it was moved"
  end 
end

Ractor.select(r1)

Output:

[INSIDE RACTOR] data value is [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and data object id is :740

data is no longer accessible because it was moved

This is because the message data is sent via yield with move:true.

In Ractor::select the move corresponds to the yield_value:

data = [{name: "tom"}, {name: "dick"}, {name: "harry"}]
r1 = Ractor.new(Ractor.current) do |main|
  puts "Received from main: #{main.take}"
end

puts "Ractor.select with yield_value: #{data} and move: true"

Ractor.select(r1, yield_value: data, move: true)

begin 
  puts "Can access data: #{data}"
rescue Ractor::MovedError => e 
  puts
  puts "data is no longer accessible because it was moved"
end 

Output:

Ractor.select with yield_value: [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}] and move: true
Received from main: [{:name=>"tom"}, {:name=>"dick"}, {:name=>"harry"}]

data is no longer accessible because it was moved

This is because the yield_value is moved and therefor no longer accessible to the sender.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52