Feature #19326
openPlease add a better API for passing a Proc to a Ractor
Description
Example 1:
class Worker
def initialize(&block)
@block = block
end
def run
Ractor.new(@block, &:call)
end
end
worker = Worker.new { 1 }
puts worker.run.take
Errors with:
<internal:ractor>:271:in `new': allocator undefined for Proc (TypeError)
from scripts/run.rb:9:in `run'
from scripts/run.rb:14:in `<main>'
Example 2:
class Worker
def initialize(&block)
@block = Ractor.make_shareable(block)
end
def run
Ractor.new(@block, &:call)
end
end
worker = Worker.new { 1 }
puts worker.run.take
Errors with:
<internal:ractor>:820:in `make_shareable': Proc's self is not shareable: #<Proc:0x00007f00394c38b8 scripts/run.rb:13> (Ractor::IsolationError)
from scripts/run.rb:5:in `initialize'
from scripts/run.rb:13:in `new'
from scripts/run.rb:13:in `<main>'
Example 3:
class Worker
def initialize(&block)
@block = Ractor.make_shareable(block)
end
def run
Ractor.new(@block, &:call)
end
end
worker = Ractor.current.instance_eval { Worker.new { 1 } }
puts worker.run.take
Works, but having Ractor.current.instance_eval
as a wrapper around the block is not ideal, as Ractor is supposed to be only an implementation detail in Worker.
I know about https://bugs.ruby-lang.org/issues/18243 and the discussion around proc.bind(nil)
. That would actually be ideal, as for the purposes if why I want this functionality I don't care what self
is in a block, and the less it has access to the better.
The general idea of Worker is to have a Ractor be able to lazily execute an arbitrary proc. And all the bindings it would need would be passed explicitly, either through args
in the constructor or through send
/receive
, so self
would really not matter.
The benefit: this would make it so concurrent code can be more easily be implemented with Ractors as currently you can execute an arbitrary proc by passing it to a Thread (but you don't get the nice data isolation).