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


A draft to make this in a single pass (but it's late, I might have missed something):

* if the object is already deeply-frozen/shareable, return, otherwise:

* mark an object as frozen if not already (so the reachable objects from it don't change)
* iterate children
* mark the object as deeply frozen

but this doesn't handle recursion.

So we could mark as deeply frozen first, and remember to undo that if we cannot freeze some object.
However, is there any object that cannot be frozen? I would think not.

So:
* if the object is already deeply-frozen/shareable, return, otherwise:

* mark an object as deeply frozen
* iterate children and recurse

Since that wouldn't call user methods, there is no need to worry about something observing an object marked as deeply frozen but not actually deeply frozen yet.
However, a different Thread could see that, so it can be a problem without a GIL.

So, I think we could use an extra bit for "visited by deep_freeze":
* if the object is already deeply-frozen/shareable or deep_freeze-visited, return, otherwise:

* mark an object as deep_freeze-visited and as frozen if not already (so the reachable objects from it don't change)
* iterate children and recurse
* mark an object as deeply frozen

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

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