Bug #21368
closedMoving objects with finalizer between Ractors crashes
Description
When an object is moved to a different Ractor, the finalizers are not copied to the new object, so it will have the FL_FINALIZE flag set but no entry in the finalizer table.
The following script crashes:
Files
Updated by peterzhu2118 (Peter Zhu) over 1 year ago
- Assignee set to ractor
Updated by zzak (zzak _) over 1 year ago
It seemed fun to patch, but I'm not sure this is correct:
https://github.com/ruby/ruby/pull/13452
Updated by hsbt (Hiroshi SHIBATA) over 1 year ago
- Status changed from Open to Assigned
Updated by osyoyu (Daisuke Aritomo) over 1 year ago
- File 0001-Bug-21368-Transfer-finalizer-when-moving-between-Rac.patch 0001-Bug-21368-Transfer-finalizer-when-moving-between-Rac.patch added
The script in the description didn't crash in my environments (the following). Explicitly calling GC.start did.
- Linux
ruby 3.5.0dev (2025-05-28T04:34:40Z master d064fd067b) +PRISM [x86_64-linux] - macOS
ruby 3.5.0dev (2025-05-28T04:34:40Z master d064fd067b) +PRISM [arm64-darwin24]
r = Ractor.new do
Ractor.receive # don't bind to a variable (let it be garbage collected)
GC.start
end
o = Object.new
ObjectSpace.define_finalizer(o, proc { |id| })
r.send(o, move: true)
r.take
I have attached an updated version of @zzak (zzak _)_'s patch which resolves this crash, but I suppose this is inappropiate since this will leak outer variables in the following case:
r = Ractor.new do
Ractor.receive
GC.start # `unshareable` happens to get read in this Ractor
end
o = Object.new
unshareable = +"hello"
ObjectSpace.define_finalizer(o, proc { |id| p unshareable })
r.send(o, move: true)
r.take
Maybe simply marking objects with a finalizer ineligible for moving is more appropiate.
Updated by zzak (zzak _) over 1 year ago
Maybe simply marking objects with a finalizer ineligible for moving is more appropiate.
Thanks for checking, I've updated the PR to raise if the object has a finalizer. I'm not sure if we should do the check in like make_shareable_check_shareable instead, for example.
Updated by osyoyu (Daisuke Aritomo) over 1 year ago
Maybe this ticket should be merged with https://bugs.ruby-lang.org/issues/21315 ?
Updated by ko1 (Koichi Sasada) 21 days ago
- Status changed from Assigned to Closed
Applied in changeset git|11a54ae0f9e1ac7d64fbf64c411e00b37e4dbe49.
Keep FL_FINALIZE on the shell an object leaves behind when it is moved
move_neutralize_source() rewrites the source's flags to
T_OBJECT | FL_FREEZE | (flags & FL_PROMOTED), which drops FL_FINALIZE
while the finalizer table entry keyed on that slot stays. The two then
disagree, and a RUBY_DEBUG build aborts at shutdown:
gc/default/default.c:4044: Assertion Failed:
rb_gc_impl_shutdown_call_finalizer_i:RB_FL_TEST(obj, FL_FINALIZE)
r = Ractor.new { Ractor.receive }
o = Object.new
ObjectSpace.define_finalizer(o, proc { |id| })
r.send(o, move: true)
Carry FL_FINALIZE over to the shell. The finalizer then runs when the
shell is collected, in the Ractor that defined it; the object rebuilt on
the other side gets fresh flags and does not inherit it, so it still runs
exactly once.
[Bug #21368]
Co-Authored-By: Claude Opus 5 (1M context) noreply@anthropic.com