Ractor: restrict class/module modification to the creator Ractor

Only the Ractor which created a class/module may modify it: define, remove or
undefine methods, alias, include/prepend, refine, define or remove constants,
register an autoload, and set the class's own instance variables.  Reading is
unchanged.  This replaces the assorted "main Ractor only" rules with one
creator-based rule, and in exchange a Ractor gets full use of the classes it
creates itself -- unshareable constant and instance variable values included,
which even the main Ractor could not do for its own classes before.

Three paths reach a class's tables without going through rb_class_modify_check,
so they get the check of their own: Module#refine (which writes its refinement
tables into the receiver as well as into the refined class), Class#initialize
(which writes the superclass of a not-yet-initialized class, and had no frozen
check either), and Module#ruby2_keywords.

The constant inline cache has to move with the rule.  Its hit condition was
"shareable value, or we are the main Ractor", which was sound only because a
non-main Ractor could not have an unshareable constant in the first place.  Now
it can, so an owner priming the cache would let any other Ractor read that value
straight out of it, never reaching the owner check in vm_get_ev_const.  The
entry records the Ractor which filled it instead -- the one that passed the
check -- which also gives a non-main owner a fast path it never had.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Koichi SasadaandClaude Fable 5 committed 2026-09-10 16:02:03 +09:00
1 parent 3f2e84dd36
commit 2e5bf429e4
10 files changed
+183 -60
Notes: git 2026-09-10 07:03:35 +00:00

No files matched your search

+24 -17
View File
@@ -1209,12 +1209,11 @@ rb_alias_variable(ID name1, ID name2)
}
static void
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(ID id)
class_ivar_set_ractor_check(VALUE klass, ID id)
{
if (UNLIKELY(!rb_ractor_main_p())) {
if (rb_is_instance_id(id)) { // check only normal ivars
rb_raise(rb_eRactorIsolationError, "can not set instance variables of classes/modules by non-main Ractors");
}
if (rb_is_instance_id(id) && // check only normal ivars
UNLIKELY(!rb_class_owned_p(klass))) {
rb_raise(rb_eRactorIsolationError, "can not set instance variables of classes/modules created by another Ractor");
}
}
@@ -1244,6 +1243,10 @@ ivar_ractor_check(VALUE obj, ID id)
UNLIKELY(!rb_ractor_main_p()) &&
UNLIKELY(rb_ractor_shareable_p(obj))) {
if (RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_MODULE)) {
// classes/modules are owner-checked at each read/write site instead
return;
}
rb_raise(rb_eRactorIsolationError, "can not access instance variables of shareable objects from non-main Ractors");
}
}
@@ -1556,11 +1559,11 @@ rb_ivar_lookup(VALUE obj, ID id, VALUE undef)
}
if (is_class && val != undef && rb_is_instance_id(id)) {
if (UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) {
if (UNLIKELY(!rb_class_owned_p(obj)) && !rb_ractor_shareable_p(val)) {
rb_raise(
rb_eRactorIsolationError,
"can not get unshareable values from instance variables of classes/modules from "
"non-main Ractors (%"PRIsVALUE" from %"PRIsVALUE")",
"can not get unshareable values from instance variables of classes/modules "
"created by another Ractor (%"PRIsVALUE" from %"PRIsVALUE")",
rb_id2str(id),
obj
);
@@ -1593,9 +1596,9 @@ rb_ivar_get_at(VALUE obj, attr_index_t index, ID id)
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
VALUE val = rb_imemo_fields_ptr(fields_obj)[index];
if (UNLIKELY(!rb_ractor_main_p()) && !rb_ractor_shareable_p(val)) {
if (UNLIKELY(!rb_class_owned_p(obj)) && !rb_ractor_shareable_p(val)) {
rb_raise(rb_eRactorIsolationError,
"can not get unshareable values from instance variables of classes/modules from non-main Ractors");
"can not get unshareable values from instance variables of classes/modules created by another Ractor");
}
return val;
@@ -1648,7 +1651,7 @@ rb_ivar_delete(VALUE obj, ID id, VALUE undef)
int type = BUILTIN_TYPE(obj);
if (type == T_CLASS || type == T_MODULE) {
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id);
class_ivar_set_ractor_check(obj, id);
if (rb_multi_ractor_p()) {
concurrent = true;
@@ -2042,7 +2045,7 @@ ivar_set(VALUE obj, ID id, VALUE val)
case T_CLASS:
case T_MODULE:
{
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(id);
class_ivar_set_ractor_check(obj, id);
bool dontcare;
return class_ivar_set(obj, id, val, &dontcare);
}
@@ -2328,7 +2331,8 @@ rb_field_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg,
case T_CLASS:
case T_MODULE:
{
IVAR_ACCESSOR_SHOULD_BE_MAIN_RACTOR(0);
// No owner check: every caller of this walk uses the names only for a
// class/module. Values are checked where they are read (rb_ivar_lookup).
VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(obj);
if (fields_obj) {
imemo_fields_each(fields_obj, func, arg, ivar_only);
@@ -2891,6 +2895,8 @@ rb_autoload_str(VALUE module, ID name, VALUE feature)
rb_raise(rb_eNameError, "autoload must be constant name: %"PRIsVALUE"", QUOTE_ID(name));
}
rb_class_owner_check(module);
Check_Type(feature, T_STRING);
if (!RSTRING_LEN(feature)) {
rb_raise(rb_eArgError, "empty feature name");
@@ -3332,9 +3338,9 @@ rb_const_get_0(VALUE klass, ID id, int exclude, int recurse, int visibility)
VALUE found_in;
VALUE c = rb_const_search(klass, id, exclude, recurse, visibility, &found_in);
if (!UNDEF_P(c)) {
if (UNLIKELY(!rb_ractor_main_p())) {
if (UNLIKELY(!rb_class_owned_p(found_in))) {
if (!rb_ractor_shareable_p(c)) {
rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" by non-main Ractor.", rb_class_path(found_in), rb_id2str(id));
rb_raise(rb_eRactorIsolationError, "can not access non-shareable objects in constant %"PRIsVALUE"::%"PRIsVALUE" of a class/module created by another Ractor.", rb_class_path(found_in), rb_id2str(id));
}
}
return c;
@@ -3546,6 +3552,7 @@ rb_const_remove(VALUE mod, ID id)
rb_const_entry_t *ce;
rb_check_frozen(mod);
rb_class_owner_check(mod);
ce = rb_const_lookup(mod, id);
@@ -3842,8 +3849,8 @@ const_set(VALUE klass, ID id, VALUE val)
QUOTE_ID(id));
}
if (!rb_ractor_main_p() && !rb_ractor_shareable_p(val)) {
rb_raise(rb_eRactorIsolationError, "can not set constants with non-shareable objects by non-main Ractors");
if (UNLIKELY(!rb_class_owned_p(klass))) {
rb_raise(rb_eRactorIsolationError, "can not set constants of classes/modules created by another Ractor");
}
check_before_mod_set(klass, id, val, "constant");