From f133ebb2db664801f87efa98aa91d610d194b700 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 18 Dec 2025 16:12:47 +0000 Subject: [PATCH 1/3] Bump RDoc to 7.0.1 (#15628) This improves several enhancements to the Aliki theme. And since Aliki also became the default theme, we don't need to specify the generator name anymore. --- .rdoc_options | 2 -- NEWS.md | 2 +- gems/bundled_gems | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.rdoc_options b/.rdoc_options index 591ddf58897595..89265cafd4d0f4 100644 --- a/.rdoc_options +++ b/.rdoc_options @@ -12,8 +12,6 @@ exclude: - \.gemspec\z - lib/set/subclass_compatible.rb -generator_name: aliki - autolink_excluded_words: - Box - Class diff --git a/NEWS.md b/NEWS.md index ebba5de74dad6c..a7a94d94287f2c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -269,7 +269,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.5.0 * logger 1.7.0 -* rdoc 6.17.0 +* rdoc 7.0.1 * win32ole 1.9.2 * irb 1.16.0 * reline 0.6.3 diff --git a/gems/bundled_gems b/gems/bundled_gems index 559b7126db1bfe..6717d7e1924180 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.17.0 https://github.com/ruby/rdoc +rdoc 7.0.1 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.16.0 https://github.com/ruby/irb reline 0.6.3 https://github.com/ruby/reline From bfd28d581c524c7a7df877f2425de9fdd8de161a Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 18 Dec 2025 12:37:27 -0500 Subject: [PATCH 2/3] 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 9716ba07dae2e2..840bdeb0c2f9e6 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 69ce9e63bd9000..8834814227b0e8 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 22078514ad5cd3..8f12e06685bb1f 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -930,4 +930,19 @@ def test_safe_multi_ractor_subclasses_list_mutation 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 From 0e719239c2fa39804cdbf1f920a18fcec919e2ce Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 18 Dec 2025 09:47:26 +0100 Subject: [PATCH 3/3] thread_sync: Mutex keep `rb_thread_t *` instead of `VALUE` We never need the actual thread object and this avoid any issue if the thread object is ever moved. --- thread_sync.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/thread_sync.c b/thread_sync.c index 6bff982f31d78b..0cf7c1faef97c1 100644 --- a/thread_sync.c +++ b/thread_sync.c @@ -8,7 +8,7 @@ static VALUE rb_eClosedQueueError; /* Mutex */ typedef struct rb_mutex_struct { rb_serial_t ec_serial; - VALUE thread; // even if the fiber is collected, we might need access to the thread in mutex_free + rb_thread_t *th; // even if the fiber is collected, we might need access to the thread in mutex_free struct rb_mutex_struct *next_mutex; struct ccan_list_head waitq; /* protected by GVL */ } rb_mutex_t; @@ -133,7 +133,7 @@ mutex_free(void *ptr) { rb_mutex_t *mutex = ptr; if (mutex_locked_p(mutex)) { - const char *err = rb_mutex_unlock_th(mutex, rb_thread_ptr(mutex->thread), 0); + const char *err = rb_mutex_unlock_th(mutex, mutex->th, 0); if (err) rb_bug("%s", err); } ruby_xfree(ptr); @@ -223,7 +223,7 @@ thread_mutex_remove(rb_thread_t *thread, rb_mutex_t *mutex) static void mutex_set_owner(rb_mutex_t *mutex, rb_thread_t *th, rb_serial_t ec_serial) { - mutex->thread = th->self; + mutex->th = th; mutex->ec_serial = ec_serial; } @@ -340,7 +340,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) } } else { - if (!th->vm->thread_ignore_deadlock && rb_thread_ptr(mutex->thread) == th) { + if (!th->vm->thread_ignore_deadlock && mutex->th == th) { rb_raise(rb_eThreadError, "deadlock; lock already owned by another fiber belonging to the same thread"); } @@ -391,7 +391,7 @@ do_mutex_lock(struct mutex_args *args, int interruptible_p) /* release mutex before checking for interrupts...as interrupt checking * code might call rb_raise() */ if (mutex->ec_serial == ec_serial) { - mutex->thread = Qfalse; + mutex->th = NULL; mutex->ec_serial = 0; } RUBY_VM_CHECK_INTS_BLOCKING(th->ec); /* may release mutex */