Project

General

Profile

Actions

Feature #21033

open

Allow lambdas that don't access `self` to be Ractor shareable

Added by tenderlovemaking (Aaron Patterson) 3 days ago. Updated 1 day ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:120643]

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


Related issues 3 (1 open2 closed)

Related to Ruby master - Bug #21039: Ractor.make_shareable breaks block semantics (seeing updated captured variables) of existing blocksOpenko1 (Koichi Sasada)Actions
Related to Ruby master - Bug #18243: Ractor.make_shareable does not freeze the receiver of a Proc but allows accessing ivars of itClosedActions
Related to Ruby master - Feature #18276: `Proc#bind_call(obj)` same as `obj.instance_exec(..., &proc_obj)`RejectedActions
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0