Feature #21033
openAllow lambdas that don't access `self` to be Ractor shareable
Description
Hi,
I would like to allow lambdas that don't access self
to be eligible for Ractor shareability regardless of the shareability status of self
.
Consider the following code:
class Foo
def make_lambda
x = 123
lambda { x }
end
end
Ractor.make_shareable(Foo.new.make_lambda)
With Ruby 3.4.X, this will raise an exception. The reason is because self
, which is an unfrozen instance of Foo
, is not shareable. However, we can see from the code that the lambda doesn't access self
. I would like to make lambdas such as the ones above eligible for shareability, and I've submitted a patch here.
I think we can detect access to self
by scanning the instructions in the lambda. Any references to putself
, getinstancevariable
, or setinstancevariable
will result in using the default behavior (checking the frozen status of self
).
Considerations¶
What about eval
?¶
I think that eval
is not a problem because calling eval has an implicit reference to self
:
$ ./miniruby --dump=insns -e 'lambda { eval("123") }'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,22)>
0000 putself ( 1)[Li]
0001 send <calldata!mid:lambda, argc:0, FCALL>, block in <main>
0004 leave
== disasm: #<ISeq:block in <main>@-e:1 (1,7)-(1,22)>
0000 putself ( 1)[LiBc]
0001 putchilledstring "123"
0003 opt_send_without_block <calldata!mid:eval, argc:1, FCALL|ARGS_SIMPLE>
0005 leave [Br]
If we try to call eval
inside the lambda, there will be an implicit putself
instruction added which means we will fall back to the old behavior.
What about binding
?¶
If you call binding
from inside the lambda
there will be a putself
instruction so we fall back to the old behavior. This is the same as the eval
case.
What about binding
via a local?¶
If you assign binding
to a local, shareability will fail because the lambda
references an unshareable local:
class Foo
def make_lambda
x = binding
lambda { x }
end
end
b = Foo.new.make_lambda
# exception because local `x` is not shareable
Ractor.make_shareable(b)
What about accessing binding
via the proc itself?¶
The lambda can references itself via a local and access binding, but again this will fail isolation when locals are scanned:
class Foo
def make_lambda
x = lambda { x.binding.eval("self") }
end
end
b = Foo.new.make_lambda
# exception because local `x` is not shareable
Ractor.make_shareable(b)
I think I've covered all cases where self
can possibly escape. I would appreciate any feedback.
Again, here is the patch.
Thanks.
Files