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.
This commit is contained in:
Luke Gruber 2025-12-18 12:37:27 -05:00 committed by GitHub
parent f133ebb2db
commit bfd28d581c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
Notes: git 2025-12-18 17:37:55 +00:00
Merged-By: luke-gru <luke.gru@gmail.com>
3 changed files with 39 additions and 14 deletions

37
class.c
View File

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

1
depend
View File

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

View File

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