From: "tenderlovemaking (Aaron Patterson) via ruby-core" Date: 2025-01-14T22:34:13+00:00 Subject: [ruby-core:120672] [Ruby master Feature#21033] Allow lambdas that don't access `self` to be Ractor shareable Issue #21033 has been updated by tenderlovemaking (Aaron Patterson). Eregon (Benoit Daloze) wrote in #note-14: > Given semantics are already incompatible when using `Ractor.make_shareable`, maybe that should change the receiver to `nil` too? We _could_ set it to a singleton object that doesn't respond to anything. Instead of just raising NoMethodError on `nil`, it would be possible for us to intercept the method call and inform the user they're attempting to touch a non-shareable self. > It doesn't feel right (IMO it's broken semantics) but since it's already incompatible with normal Ruby semantics, might as well make it useful? > Is changing self worse than preventing to see captured variables updates? (I don't know) > WDYT @ko1? > > Given that snippet behavior I think `Ractor.make_shareable(Proc)` should at least return a new Proc to not mutate the argument that way, then the Ruby block semantics would at least be preserved for usages using that Proc directly. Until this ticket, I was operating under the assumption it worked this way ����. I agree with you. > I think a proper solution would be a way to declare an isolated block (to be precise, using `nil` as `self`; and either allowing no captured variables like `Ractor.new`'s block or only allowing shareable captured variables and taking a snapshot of them i.e. not seeing re-assignments), either with a new method or new syntax (which could then do the captured variables checks at parse time for the no captures variant). > Then it's fair for those isolated blocks to have different semantics, vs mutating existing Ruby blocks to have different semantics if `Ractor.make_shareable` has ever been called on them. I would like that, but as I mentioned, I don't want to change all call sites (if possible). At least new code could take advantage of such a syntax. ---------------------------------------- Feature #21033: Allow lambdas that don't access `self` to be Ractor shareable https://bugs.ruby-lang.org/issues/21033#change-111496 * 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: #@-e:1 (1,0)-(1,22)> 0000 putself ( 1)[Li] 0001 send , block in
0004 leave == disasm: #@-e:1 (1,7)-(1,22)> 0000 putself ( 1)[LiBc] 0001 putchilledstring "123" 0003 opt_send_without_block 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. ---Files-------------------------------- 0001-Allow-lambdas-that-don-t-access-self-to-be-made-shar.patch (6.51 KB) -- 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/