From bfd28d581c524c7a7df877f2425de9fdd8de161a Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 18 Dec 2025 12:37:27 -0500 Subject: [PATCH] make rb_singleton_class ractor safe (#15591) Since singleton classes are created lazily, we need to make sure that we lock around their creation. Unfortunately, that means we need to lock around every shareable object's call to `singleton_class`, including classes and modules. --- class.c | 37 +++++++++++++++++++++++-------------- depend | 1 + test/ruby/test_class.rb | 15 +++++++++++++++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/class.c b/class.c index 9716ba07da..840bdeb0c2 100644 --- a/class.c +++ b/class.c @@ -30,6 +30,7 @@ #include "internal/variable.h" #include "ruby/st.h" #include "vm_core.h" +#include "ruby/ractor.h" #include "yjit.h" #include "zjit.h" @@ -2823,7 +2824,7 @@ rb_special_singleton_class(VALUE obj) * consistency of the metaclass hierarchy. */ static VALUE -singleton_class_of(VALUE obj) +singleton_class_of(VALUE obj, bool ensure_eigenclass) { VALUE klass; @@ -2851,13 +2852,26 @@ singleton_class_of(VALUE obj) } } - klass = METACLASS_OF(obj); - if (!(RCLASS_SINGLETON_P(klass) && - RCLASS_ATTACHED_OBJECT(klass) == obj)) { - klass = rb_make_metaclass(obj, klass); + bool needs_lock = rb_multi_ractor_p() && rb_ractor_shareable_p(obj); + unsigned int lev; + if (needs_lock) { + RB_VM_LOCK_ENTER_LEV(&lev); + } + { + klass = METACLASS_OF(obj); + if (!(RCLASS_SINGLETON_P(klass) && + RCLASS_ATTACHED_OBJECT(klass) == obj)) { + klass = rb_make_metaclass(obj, klass); + } + RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); + if (ensure_eigenclass && RB_TYPE_P(obj, T_CLASS)) { + /* ensures an exposed class belongs to its own eigenclass */ + (void)ENSURE_EIGENCLASS(klass); + } + } + if (needs_lock) { + RB_VM_LOCK_LEAVE_LEV(&lev); } - - RB_FL_SET_RAW(klass, RB_OBJ_FROZEN_RAW(obj)); return klass; } @@ -2900,12 +2914,7 @@ rb_singleton_class_get(VALUE obj) VALUE rb_singleton_class(VALUE obj) { - VALUE klass = singleton_class_of(obj); - - /* ensures an exposed class belongs to its own eigenclass */ - if (RB_TYPE_P(obj, T_CLASS)) (void)ENSURE_EIGENCLASS(klass); - - return klass; + return singleton_class_of(obj, true); } /*! @@ -2923,7 +2932,7 @@ rb_singleton_class(VALUE obj) void rb_define_singleton_method(VALUE obj, const char *name, VALUE (*func)(ANYARGS), int argc) { - rb_define_method(singleton_class_of(obj), name, func, argc); + rb_define_method(singleton_class_of(obj, false), name, func, argc); } #ifdef rb_define_module_function diff --git a/depend b/depend index 69ce9e63bd..8834814227 100644 --- a/depend +++ b/depend @@ -1403,6 +1403,7 @@ class.$(OBJEXT): {$(VPATH)}missing.h class.$(OBJEXT): {$(VPATH)}node.h class.$(OBJEXT): {$(VPATH)}onigmo.h class.$(OBJEXT): {$(VPATH)}oniguruma.h +class.$(OBJEXT): {$(VPATH)}ractor.h class.$(OBJEXT): {$(VPATH)}ruby_assert.h class.$(OBJEXT): {$(VPATH)}ruby_atomic.h class.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 22078514ad..8f12e06685 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -930,4 +930,19 @@ CODE end.each(&:join) end; end + + def test_safe_multi_ractor_singleton_class_access + assert_ractor "#{<<~"begin;"}\n#{<<~'end;'}" + begin; + class A; end + 4.times.map do + Ractor.new do + a = A + 100.times do + a = a.singleton_class + end + end + end.each(&:join) + end; + end end