Issue #17145 has been updated by ko1 (Koichi Sasada).


marcandre (Marc-Andre Lafortune) wrote in #note-16:
> 1) This does not work for recursive structures, in the sense that they will not be marked as Ractor shareable. This is capital, particularly since it is the (only) part of this method that can not be done in pure Ruby.

Current implementation doesn't mark as shareable, but we can make it. The problem is what happens on half-frozen case... Half-shareable is not acceptable. If half-frozen is acceptable, freezing with traversing, and mark as shareable at second phase is possible.

> 2) There is no callback mechanism for user classes (since this does not call `#freeze`). Typical use case would be for expensive calculations that are done lazily. A class may want to pre-build them before caching. There should be some callback mechanism. My opinion is that we should worry about things working well for well-programmed cases *before* we worry about exceptions (e.g. cases that fail do deeply freeze).
> I think that freezing a class "behind its back" by not calling `#freeze` breaks the contract and could be a source of incompatibility.
> I see no incompatibility with the two pass system and calling `#freeze` on the list of reachable objects. I have no issue with a structure being half frozen if things fail; the developper was ready to deep freeze all of it, so having only half frozen , even though it clearly was not the intention, does not feel like a big issue.

`$ gem-codesearch 'def freeze' | gist -p` => https://gist.github.com/ko1/63b8d43218884249e782d63c2f27b927

I never realized that so many `freeze` redefinition are used. Checking the code, some of them freeze attribute objects, which they are frozen with `deep_freeze`. I can find some cases to calculate lazy (?).

mmm. Maybe this method is used with literals, so there is a chance to optimize for such cases.

I still not sure we can remain the half-frozen state, so I want to ask other comments.

----------------------------------------
Feature #17145: Ractor-aware `Object#deep_freeze`
https://bugs.ruby-lang.org/issues/17145#change-88056

* Author: marcandre (Marc-Andre Lafortune)
* Status: Open
* Priority: Normal
----------------------------------------
I'd like to propose `Object#deep_freeze`:

Freezes recursively the contents of the receiver (by calling `deep_freeze`) and
then the receiver itself (by calling `freeze`).
Values that are shareable via `Ractor` (e.g. classes) are never frozen this way.

```ruby
# freezes recursively:
ast = [:hash, [:pair, [:str, 'hello'], [:sym, :world]]].deep_freeze
ast.dig(1, 1) # => [:str, 'hello']
ast.dig(1, 1).compact! # => FrozenError

# does not freeze classes:
[[String]].deep_freeze
String.frozen? # => false

# calls `freeze`:
class Foo
  def freeze
    build_cache!
    puts "Ready for freeze"
    super
  end
  # ...
end
[[[Foo.new]]].deep_freeze # => Outputs "Ready for freeze"
```


I think a variant `deep_freeze!` that raises an exception if the result isn't Ractor-shareable would be useful too:

```ruby
class Fire
  def freeze
    # do not call super
  end
end

x = [Fire.new]
x.deep_freeze! # => "Could not be deeply-frozen: #<Fire:0x00007ff151994748>"
```



-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>