Issue #17145 has been updated by Eregon (Benoit Daloze).


ko1 (Koichi Sasada) wrote in #note-14:
> I implemented `Object#deep_freeze(skip_shareable: false)` for trial.

This sounds good to me.

IMHO if something fails in the middle, it's OK for part of it to be frozen.
The intention was to freeze the whole object graph anyway.
The code using the object graph after `deep_freeze` assumes it was frozen, there shouldn't be any issue if some of it is frozen, the code expected that.
(also such code would only run if the exception is caught)

In some languages with unification (I know of Oz), it's also accepted that if it fails in the middle, then the partial state is still there and not magically undone.

I don't think we should call user methods, that will require a lot more scanning (before & after calling user `freeze`), and if some  user`freeze` would create new objects and attach them to the object graph, `deep_freeze` could never end.
Is there a concrete case where it is problematic to not call user's `freeze`?

I think the design should allow to do everything in a single pass for efficiency, and notably use the "deep frozen/shareable" bit as a "already visited object" marker.
This is also how I implemented "Deep Sharing" during my PhD in https://eregon.me/blog/assets/research/thread-safe-objects.pdf section 5.3, and I think we should allow the same optimization to be used for `deep_freeze` because it's a large performance gain compared to a generic routine.

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

* 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>