From: "tenderlovemaking (Aaron Patterson) via ruby-core" <ruby-core@...> Date: 2025-01-13T23:17:24+00:00 Subject: [ruby-core:120643] [Ruby master Feature#21033] Allow lambdas that don't access `self` to be Ractor shareable Issue #21033 has been reported by tenderlovemaking (Aaron Patterson). ---------------------------------------- Feature #21033: Allow lambdas that don't access `self` to be Ractor shareable https://bugs.ruby-lang.org/issues/21033 * Author: tenderlovemaking (Aaron Patterson) * Status: Open ---------------------------------------- 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: ```ruby 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](https://github.com/ruby/ruby/pull/12567). 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: ```ruby 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: ```ruby 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](https://github.com/ruby/ruby/pull/12567). Thanks. -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/