Skip to content

Commit 8b8497b

Browse files
committed
Apply ko1 patch
1 parent aaeb451 commit 8b8497b

9 files changed

Lines changed: 252 additions & 59 deletions

File tree

bootstraptest/test_ractor.rb

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -845,8 +845,8 @@ def ractor_local_globals
845845
Ractor.new { inner = 99; eval("inner").to_s }.value
846846
}
847847

848-
# ivar in shareable-objects are not allowed to access from non-main Ractor
849-
assert_equal "can not get unshareable values from instance variables of classes/modules from non-main Ractors (@iv from C)", <<~'RUBY', frozen_string_literal: false
848+
# ivar in shareable-objects are not allowed to access from non-owner Ractor
849+
assert_equal "can not get unshareable values from instance variables of classes/modules created by another Ractor (@iv from C)", <<~'RUBY', frozen_string_literal: false
850850
class C
851851
@iv = 'str'
852852
end
@@ -1189,7 +1189,7 @@ def self.cv?
11891189
}
11901190
11911191
# Getting non-shareable objects via constants by other Ractors is not allowed
1192-
assert_equal 'can not access non-shareable objects in constant C::CONST by non-main Ractor.', <<~'RUBY', frozen_string_literal: false
1192+
assert_equal 'can not access non-shareable objects in constant C::CONST of a class/module created by another Ractor.', <<~'RUBY', frozen_string_literal: false
11931193
class C
11941194
CONST = 'str'
11951195
end
@@ -1204,7 +1204,7 @@ class C
12041204
RUBY
12051205
12061206
# Constant cache should care about non-shareable constants
1207-
assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false
1207+
assert_equal "can not access non-shareable objects in constant Object::STR of a class/module created by another Ractor.", <<~'RUBY', frozen_string_literal: false
12081208
STR = "hello"
12091209
def str; STR; end
12101210
s = str() # fill const cache
@@ -1216,7 +1216,7 @@ def str; STR; end
12161216
RUBY
12171217
12181218
# The correct constant path shall be reported
1219-
assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false
1219+
assert_equal "can not access non-shareable objects in constant Object::STR of a class/module created by another Ractor.", <<~'RUBY', frozen_string_literal: false
12201220
STR = "hello"
12211221
module M
12221222
def self.str; STR; end
@@ -1229,8 +1229,8 @@ def self.str; STR; end
12291229
end
12301230
RUBY
12311231
1232-
# Setting non-shareable objects into constants by other Ractors is not allowed
1233-
assert_equal 'can not set constants with non-shareable objects by non-main Ractors', <<~'RUBY', frozen_string_literal: false
1232+
# Setting constants of classes created by other Ractors is not allowed
1233+
assert_equal 'can not set constants of classes/modules created by another Ractor', <<~'RUBY', frozen_string_literal: false
12341234
class C
12351235
end
12361236
r = Ractor.new do
@@ -1775,27 +1775,25 @@ class C8; def self.foo = 17; end
17751775
}
17761776
17771777
# check method cache invalidation
1778+
# (the owner Ractor redefines methods while another Ractor calls them)
17781779
assert_equal 'true', %q{
17791780
class Foo
17801781
def hello = nil
17811782
end
17821783
1783-
r1 = Ractor.new do
1784-
1000.times do
1785-
class Foo
1786-
def hello = nil
1787-
end
1788-
end
1789-
end
1790-
17911784
r2 = Ractor.new do
17921785
1000.times do
17931786
o = Foo.new
17941787
o.hello
17951788
end
17961789
end
17971790
1798-
r1.value
1791+
1000.times do
1792+
class Foo
1793+
def hello = nil
1794+
end
1795+
end
1796+
17991797
r2.value
18001798
18011799
true
@@ -2640,21 +2638,23 @@ def call_test(obj)
26402638
assert_equal 'ok', <<~'RUBY'
26412639
26422640
begin
2643-
CLASSES = 1000.times.map { Class.new }.freeze
2644-
2641+
# Each Ractor creates its own class (it can only define bmethods on classes
2642+
# it owns) and returns it after defining the bmethod.
26452643
# This would be better to run in parallel, but there's a bug with lambda
26462644
# creation and YJIT causing crashes in dev mode
2647-
ractors = CLASSES.map do |klass|
2648-
Ractor.new(klass) do |klass|
2645+
ractors = 1000.times.map do
2646+
Ractor.new do
2647+
klass = Class.new
26492648
Ractor.receive
26502649
klass.define_method(:foo) {}
2650+
klass
26512651
end
26522652
end
26532653
2654-
ractors.each do |ractor|
2654+
CLASSES = ractors.map do |ractor|
26552655
ractor << nil
2656-
ractor.join
2657-
end
2656+
ractor.value
2657+
end.freeze
26582658
26592659
ractors.clear
26602660
GC.start

class.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "ruby/st.h"
3232
#include "vm_core.h"
3333
#include "ruby/ractor.h"
34+
#include "ractor_core.h"
3435
#include "yjit.h"
3536
#include "zjit.h"
3637

@@ -675,6 +676,13 @@ class_alloc0(enum ruby_value_type type, VALUE klass, bool boxable)
675676

676677
memset(RCLASS_EXT_PRIME(obj), 0, sizeof(rb_classext_t));
677678

679+
// The creating Ractor owns the new class/module (owner_ractor == 0 means
680+
// the main Ractor; iclasses have no meaningful owner). Singleton classes
681+
// of classes/modules override this to inherit the attached object's owner.
682+
if (type != T_ICLASS && UNLIKELY(!rb_ractor_main_p())) {
683+
RCLASS_EXT_PRIME(obj)->owner_ractor = GET_RACTOR()->pub.self;
684+
}
685+
678686
/* ZALLOC
679687
RCLASS_CONST_TBL(obj) = 0;
680688
RCLASS_M_TBL(obj) = 0;
@@ -704,6 +712,36 @@ class_alloc(enum ruby_value_type type, VALUE klass)
704712
return class_alloc0(type, klass, boxable);
705713
}
706714

715+
bool
716+
rb_class_owned_p(VALUE klass)
717+
{
718+
// The owner Ractor object is marked from the classext, so the comparison
719+
// is never against a dangling/reused reference. Classes whose owner
720+
// Ractor has terminated are permanently read-only for everybody.
721+
VALUE owner = RCLASS_OWNER_RACTOR(klass);
722+
if (LIKELY(!owner)) {
723+
// owner_ractor == 0 means the class is owned by the main Ractor.
724+
// The main Ractor owns it unless the current thread is subject to
725+
// Ractor isolation rules (a real non-main Ractor, or the main Ractor
726+
// running under Ractor.check_isolation), in which case it is treated
727+
// as owned by nobody.
728+
return !rb_ractor_isolation_check_active();
729+
}
730+
else {
731+
return owner == GET_RACTOR()->pub.self;
732+
}
733+
}
734+
735+
void
736+
rb_class_owner_check(VALUE klass)
737+
{
738+
if (UNLIKELY(!rb_class_owned_p(klass))) {
739+
rb_raise(rb_eRactorIsolationError,
740+
"can not modify %"PRIsVALUE" because it is created by another Ractor",
741+
rb_class_path(klass));
742+
}
743+
}
744+
707745
static VALUE
708746
class_associate_super(VALUE klass, VALUE super, bool init)
709747
{
@@ -1023,6 +1061,43 @@ rb_module_check_initializable(VALUE mod)
10231061
}
10241062
}
10251063

1064+
static enum rb_id_table_iterator_result
1065+
init_copy_check_const_i(ID id, VALUE v, void *data)
1066+
{
1067+
const rb_const_entry_t *ce = (const rb_const_entry_t *)v;
1068+
if (!UNDEF_P(ce->value) && !rb_ractor_shareable_p(ce->value)) {
1069+
rb_raise(rb_eRactorIsolationError,
1070+
"can not copy a class/module created by another Ractor because "
1071+
"constant %"PRIsVALUE" refers to an unshareable object", rb_id2str(id));
1072+
}
1073+
return ID_TABLE_CONTINUE;
1074+
}
1075+
1076+
static int
1077+
init_copy_check_field_i(ID id, VALUE val, st_data_t arg)
1078+
{
1079+
if ((rb_is_instance_id(id) || rb_is_class_id(id)) && !rb_ractor_shareable_p(val)) {
1080+
rb_raise(rb_eRactorIsolationError,
1081+
"can not copy a class/module created by another Ractor because "
1082+
"variable %"PRIsVALUE" refers to an unshareable object", rb_id2str(id));
1083+
}
1084+
return ST_CONTINUE;
1085+
}
1086+
1087+
// When copying a class/module created by another Ractor, the copy belongs to
1088+
// the current Ractor, so its tables must not leak unshareable objects owned
1089+
// by the source's Ractor.
1090+
static void
1091+
init_copy_owner_check(VALUE orig)
1092+
{
1093+
if (!rb_class_owned_p(orig)) {
1094+
if (RCLASS_CONST_TBL(orig)) {
1095+
rb_id_table_foreach(RCLASS_CONST_TBL(orig), init_copy_check_const_i, NULL);
1096+
}
1097+
rb_ivar_foreach_buffered(orig, init_copy_check_field_i, 0);
1098+
}
1099+
}
1100+
10261101
/* :nodoc: */
10271102
VALUE
10281103
rb_mod_init_copy(VALUE clone, VALUE orig)
@@ -1045,6 +1120,8 @@ rb_mod_init_copy(VALUE clone, VALUE orig)
10451120
RUBY_ASSERT(RB_TYPE_P(orig, T_CLASS) || RB_TYPE_P(orig, T_MODULE));
10461121
RUBY_ASSERT(BUILTIN_TYPE(clone) == BUILTIN_TYPE(orig));
10471122

1123+
init_copy_owner_check(orig);
1124+
10481125
rb_class_set_initialized(clone);
10491126

10501127
/* cloned flag is refer at constant inline cache
@@ -1274,6 +1351,9 @@ make_metaclass(VALUE klass)
12741351
VALUE metaclass = class_boot_boxable(Qundef, FL_TEST_RAW(klass, RCLASS_BOXABLE));
12751352

12761353
FL_SET(metaclass, FL_SINGLETON);
1354+
// A metaclass is owned by the owner of the class it is attached to,
1355+
// not by the Ractor which happened to trigger its lazy creation.
1356+
RCLASS_SET_OWNER_RACTOR(metaclass, RCLASS_OWNER_RACTOR(klass));
12771357
rb_singleton_class_attached(metaclass, klass);
12781358

12791359
if (META_CLASS_OF_CLASS_CLASS_P(klass)) {
@@ -1309,6 +1389,11 @@ make_singleton_class(VALUE obj)
13091389
VALUE orig_class = METACLASS_OF(obj);
13101390
VALUE klass = class_alloc0(T_CLASS, rb_cClass, FL_TEST_RAW(orig_class, RCLASS_BOXABLE));
13111391
FL_SET(klass, FL_SINGLETON);
1392+
if (RB_TYPE_P(obj, T_MODULE)) {
1393+
// The singleton class of a module is owned by the module's owner,
1394+
// not by the Ractor which happened to trigger its lazy creation.
1395+
RCLASS_SET_OWNER_RACTOR(klass, RCLASS_OWNER_RACTOR(obj));
1396+
}
13121397
class_initialize_method_table(klass);
13131398
class_associate_super(klass, orig_class, true);
13141399
if (orig_class && !UNDEF_P(orig_class)) {

doc/language/ractor.md

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,52 @@ To isolate unshareable objects across ractors, we introduced additional language
345345

346346
Note that when not using ractors, these additional semantics are not needed (100% compatible with Ruby 2).
347347

348+
### Class and module ownership
349+
350+
Every class/module records the Ractor that created it as its *owner*. Only the owner Ractor can modify the class/module:
351+
352+
* defining, removing or undefining methods, `alias`, and changing method visibility
353+
* `include`/`prepend` into the class/module, and refining it with `Module#refine`
354+
* defining or removing constants, and registering `autoload`
355+
* setting instance variables of the class/module object
356+
357+
Reading (calling methods, instantiating, reading constants and instance variables, subclassing, and so on) is allowed from any Ractor as before.
358+
359+
All classes/modules defined at boot or by code run by the main Ractor (including `require`d libraries) are owned by the main Ractor, so non-main ractors can not monkey-patch them:
360+
361+
```ruby
362+
r = Ractor.new do
363+
class String # reopening itself is harmless, but...
364+
def foo; end # ...defining a method on a class created by
365+
end # another Ractor raises
366+
end
367+
begin
368+
r.join
369+
rescue Ractor::RemoteError => e
370+
e.cause.message #=> "can not modify String because it is created by another Ractor"
371+
end
372+
```
373+
374+
In exchange, a Ractor can fully use the classes/modules it created itself, including things which were previously allowed only on the main Ractor:
375+
376+
```ruby
377+
Ractor.new do
378+
k = Class.new do
379+
def hello = "hello"
380+
end
381+
k.const_set(:CONST, [1, 2, 3]) # even unshareable constant values
382+
k.instance_variable_set(:@iv, [4, 5]) # even unshareable ivar values
383+
k.new.hello
384+
end.value #=> "hello"
385+
```
386+
387+
Notes:
388+
389+
* A singleton class (and a metaclass) is owned by the owner of the object it is attached to, not by the Ractor which happened to trigger its lazy creation. So `def C.foo` is allowed exactly for the owner of `C`.
390+
* Classes/modules whose owner Ractor has terminated become permanently read-only for every Ractor.
391+
* Since defining a constant in a class/module created by another Ractor is prohibited, a non-main Ractor can not define a top-level class name (it would write a constant into `Object`). Define classes under your own namespace instead: `m = Module.new; m.const_set(:Foo, Class.new)`.
392+
* Copying a class/module created by another Ractor with `Class#dup`/`Object#clone` creates a copy owned by the copying Ractor; it raises `Ractor::IsolationError` if the source's constants or instance variables refer to unshareable objects.
393+
348394
### Global variables
349395

350396
Only the main Ractor can access global variables.
@@ -366,7 +412,7 @@ Note that some special global variables, such as `$stdin`, `$stdout` and `$stder
366412

367413
### Instance variables of shareable objects
368414

369-
Instance variables of classes/modules can be accessed from non-main ractors only if their values are shareable objects.
415+
Instance variables of classes/modules can be accessed from non-owner ractors only if their values are shareable objects.
370416

371417
```ruby
372418
class C
@@ -380,7 +426,7 @@ p Ractor.new do
380426
end.value #=> 1
381427
```
382428

383-
Otherwise, only the main Ractor can access instance variables of shareable objects.
429+
Otherwise, only the owner Ractor can access instance variables of classes/modules. Setting them is prohibited for non-owner ractors regardless of the value.
384430

385431
```ruby
386432
class C
@@ -393,14 +439,14 @@ Ractor.new do
393439
p @iv
394440
rescue Ractor::IsolationError
395441
p $!.message
396-
#=> "can not get unshareable values from instance variables of classes/modules from non-main Ractors"
442+
#=> "can not get unshareable values from instance variables of classes/modules created by another Ractor (@iv from C)"
397443
end
398444

399445
begin
400446
@iv = 42
401447
rescue Ractor::IsolationError
402448
p $!.message
403-
#=> "can not set instance variables of classes/modules by non-main Ractors"
449+
#=> "can not set instance variables of classes/modules created by another Ractor"
404450
end
405451
end
406452
end.join
@@ -423,7 +469,7 @@ end
423469

424470
### Class variables
425471

426-
Only the main Ractor can access class variables.
472+
Class variables are shared across the whole inheritance chain (and the class they are actually stored in can even change over time), so no single owner Ractor can be defined for them. Therefore they are not covered by the ownership relaxation above: only the main Ractor can write class variables, and only into classes/modules it owns. Non-shareable class variable values can only be read by the main Ractor.
427473

428474
```ruby
429475
class C
@@ -446,7 +492,7 @@ end
446492

447493
### Constants
448494

449-
Only the main Ractor can read constants which refer to an unshareable object.
495+
Only the owner Ractor of the class/module the constant is defined in can read constants which refer to an unshareable object.
450496

451497
```ruby
452498
class C
@@ -462,7 +508,7 @@ rescue => e
462508
end
463509
```
464510

465-
Only the main Ractor can define constants which refer to an unshareable object.
511+
Defining constants in a class/module created by another Ractor is prohibited, regardless of the value. The owner can define constants with any values, but constants which refer to unshareable objects can only be read back by the owner.
466512

467513
```ruby
468514
class C

eval.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,7 @@ rb_class_modify_check(VALUE klass)
441441
}
442442
rb_error_frozen_object(klass);
443443
}
444+
rb_class_owner_check(klass);
444445
}
445446

446447
NORETURN(static void rb_longjmp(rb_execution_context_t *, enum ruby_tag_type, volatile VALUE, VALUE));
@@ -1616,6 +1617,10 @@ rb_mod_refine(VALUE module, VALUE klass)
16161617

16171618
ensure_class_or_module(klass);
16181619

1620+
// Refining a class physically installs refined method entries into the
1621+
// target class's method table, so the target must be owned.
1622+
rb_class_owner_check(klass);
1623+
16191624
rb_refinement_setup(&data, module, klass);
16201625

16211626
rb_yield_refine_block(data.refinement, data.refinements);

0 commit comments

Comments
 (0)