It does seem like it's a problem that this is a way to have access to an unshareable object from multiple ractors
defbuild_finalizer(object)previous_ractor=Ractor.currentproc{puts"finalizing on #{Ractor.current} (was: #{previous_ractor}). Passed object: #{object}"}endr=Ractor.new{obj1=Object.newobj2=Object.newObjectSpace.define_finalizer(obj1,build_finalizer(obj2))obj1=nil# allow obj1 to be GC'dRactor.yield:oksleep1# We're still running and could access obj2}r.takeGC.startr.take
So we've given access to an object to the main Ractor even though it should not be shareable. It seems like finalizers run on an arbitrary thread so this could also be a problem in the other direction (unshareable object from main Ractor used as part of finalizer on another).
It seems like we should either:
Run each finalizer in the Ractor that created it (what do we do about terminated ractors?)
Disallow finalizers in non-main-Ractors (seems restrictive)
Only allow shareable procs for finalizers in Ractors (seems difficult for users)
I wonder if @ko1's Ractor-local GC will help with this
Run each finalizer in the Ractor that created it (what do we do about terminated ractors?)
That one I think would be doable, we could record the current ractor in with the finalizer.
For terminated Ractors I don't think it's a big deal, as long as we do what is necessary so that Ractor.current and Ractor local storage still work as expected.
When a Ractor terminates just before it actually does so it should probably run the remaining finalizers, AFAIK, this is what CRuby does when the process is about to terminate and there are finalizers not run yet (but I might be wrong, though it clearly seems to exhibit that behavior).
3 is a no-no, it's already quite difficult to write correct finalizers (which don't capture the object to finalize), it's basically impossible to write them as shareable procs.
When a Ractor terminates just before it actually does so it should probably run the remaining finalizers
Objects aren't necessarily unreferenced/garbage collectable at this point (they could have been made shareable, or be the return value taken by a another Ractor). I guess we could do this, but it does seem extremely surprising behaviour to run a finalizer on an accessible live object.
For terminated Ractors I don't think it's a big deal, as long as we do what is necessary so that Ractor.current and Ractor local storage still work as expected.
The final return value of a Ractor is not made shareable, so the take-ing Ractor may have access with whatever objects were captured in the finalizer proc. I think we probably need to track that and forward the finalizer to the "next" Ractor (possibly following that several levels until we find a still-running Ractor).
it does seem extremely surprising behaviour to run a finalizer on an accessible live object.
I agree, I haven't yet fully understood how this works in CRuby.
We tried to replicate the behavior in TruffleRuby but it leds to issues because some objects might depend on other objects for finalization, or rely on finalizers of objects they reference to not run before, and so the order can matter and potentially cause segfaults if run in the wrong order.