summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Gruber <luke.gruber@shopify.com>2025-12-18 12:37:27 -0500
committerGitHub <noreply@github.com>2025-12-18 12:37:27 -0500
commitbfd28d581c524c7a7df877f2425de9fdd8de161a (patch)
treeed9d77592680e0d2f5b1a28e273282cb6fb06296
parentf133ebb2db664801f87efa98aa91d610d194b700 (diff)
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.
-rw-r--r--class.c37
-rw-r--r--depend1
-rw-r--r--test/ruby/test_class.rb15
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