Skip to content

Commit 75cd024

Browse files
committed
Add tests for Ractor.check_isolation
Cover the check_isolation predicate, block return value, block requirement, nested-state restore, warning silencing, and thread inheritance, plus the downgrade-to-warning behaviour at every isolation site: class ivar get/set, class variable access, constant access (including the cached inline-cache path), global variable access, Ractor.make_shareable, Ractor.shareable_proc, and ractor-unsafe C method calls. Also assert that violations still raise in a real non-main Ractor.
1 parent 0926539 commit 75cd024

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

test/ruby/test_ractor.rb

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,177 @@ module ModuleWithUnshareableConstant
393393
RUBY
394394
end
395395

396+
# Ractor.check_isolation { ... } forces isolation checks to behave as if the
397+
# code ran in a non-main Ractor, but downgrades the resulting
398+
# Ractor::IsolationError raises to :ractor_isolation category warnings.
399+
def test_check_isolation_predicate_defaults_to_false
400+
assert_ractor(<<~'RUBY')
401+
assert_equal false, Ractor.check_isolation?
402+
RUBY
403+
end
404+
405+
def test_check_isolation_is_enabled_only_inside_the_block
406+
assert_ractor(<<~'RUBY')
407+
inside = nil
408+
Ractor.check_isolation { inside = Ractor.check_isolation? }
409+
assert_equal true, inside
410+
assert_equal false, Ractor.check_isolation?
411+
RUBY
412+
end
413+
414+
def test_check_isolation_returns_the_block_value
415+
assert_ractor(<<~'RUBY')
416+
assert_equal :returned, Ractor.check_isolation { :returned }
417+
RUBY
418+
end
419+
420+
def test_check_isolation_requires_a_block
421+
assert_ractor(<<~'RUBY')
422+
assert_raise(ArgumentError) { Ractor.check_isolation }
423+
RUBY
424+
end
425+
426+
def test_check_isolation_warns_instead_of_raising
427+
assert_ractor(<<~'RUBY')
428+
class CheckIsolationFixture
429+
@ivar = "ivar" # not shareable
430+
@@cvar = [1, 2, 3] # not shareable
431+
MUTABLE = "mutable" # not shareable
432+
end
433+
$check_isolation_gvar = "global"
434+
435+
assert_warning(%r{instance variables of classes/modules from non-main Ractors}) do
436+
Ractor.check_isolation { CheckIsolationFixture.instance_variable_get(:@ivar) }
437+
end
438+
assert_warning(/can not read non-shareable class variable @@cvar from non-main Ractors/) do
439+
Ractor.check_isolation { CheckIsolationFixture.class_variable_get(:@@cvar) }
440+
end
441+
assert_warning(/non-shareable objects in constant CheckIsolationFixture::MUTABLE/) do
442+
Ractor.check_isolation { CheckIsolationFixture::MUTABLE }
443+
end
444+
assert_warning(/can not access global variable \$check_isolation_gvar from non-main Ractor/) do
445+
Ractor.check_isolation { $check_isolation_gvar }
446+
end
447+
assert_warning(%r{can not set instance variables of classes/modules by non-main Ractors}) do
448+
Ractor.check_isolation { CheckIsolationFixture.instance_variable_set(:@ivar, "new") }
449+
end
450+
RUBY
451+
end
452+
453+
def test_check_isolation_does_not_warn_for_shareable_values
454+
assert_ractor(<<~'RUBY')
455+
class CheckIsolationFixture
456+
FROZEN = "frozen".freeze # shareable
457+
end
458+
assert_no_warning(/non-main Ractor/) do
459+
Ractor.check_isolation { CheckIsolationFixture::FROZEN }
460+
end
461+
RUBY
462+
end
463+
464+
def test_check_isolation_restores_state_when_nested
465+
assert_ractor(<<~'RUBY')
466+
# Capture the states inside the blocks and assert outside: running an
467+
# assertion inside check_isolation would itself trip an isolation
468+
# warning (the harness bumps an assertion counter on a module ivar).
469+
states = []
470+
Ractor.check_isolation do
471+
states << Ractor.check_isolation?
472+
Ractor.check_isolation do
473+
states << Ractor.check_isolation?
474+
end
475+
states << Ractor.check_isolation?
476+
end
477+
assert_equal [true, true, true], states
478+
assert_equal false, Ractor.check_isolation?
479+
RUBY
480+
end
481+
482+
def test_check_isolation_can_be_silenced
483+
assert_ractor(<<~'RUBY')
484+
class CheckIsolationFixture
485+
@ivar = "ivar"
486+
end
487+
Warning[:ractor_isolation] = false
488+
assert_no_warning(/non-main Ractor/) do
489+
Ractor.check_isolation { CheckIsolationFixture.instance_variable_get(:@ivar) }
490+
end
491+
RUBY
492+
end
493+
494+
def test_check_isolation_is_inherited_by_threads_started_inside
495+
assert_ractor(<<~'RUBY')
496+
inside = outside = nil
497+
Ractor.check_isolation do
498+
Thread.new { inside = Ractor.check_isolation? }.join
499+
end
500+
Thread.new { outside = Ractor.check_isolation? }.join
501+
assert_equal true, inside
502+
assert_equal false, outside
503+
RUBY
504+
end
505+
506+
def test_isolation_violation_still_raises_in_a_real_ractor
507+
assert_ractor(<<~'RUBY')
508+
class CheckIsolationFixture
509+
@ivar = "ivar"
510+
end
511+
e = Ractor.new do
512+
CheckIsolationFixture.instance_variable_get(:@ivar)
513+
rescue => exc
514+
exc
515+
end.value
516+
assert_kind_of Ractor::IsolationError, e
517+
RUBY
518+
end
519+
520+
def test_check_isolation_warns_on_cached_constant_access
521+
assert_ractor(<<~'RUBY')
522+
class CheckIsolationFixture
523+
MUTABLE = "mutable"
524+
end
525+
# Access the same constant repeatedly so the inline cache is populated;
526+
# the isolation check must still fire on the cached / JIT fast path.
527+
assert_warning(/non-shareable objects in constant CheckIsolationFixture::MUTABLE/) do
528+
Ractor.check_isolation { 3.times { CheckIsolationFixture::MUTABLE } }
529+
end
530+
RUBY
531+
end
532+
533+
def test_check_isolation_warns_for_make_shareable
534+
assert_ractor(<<~'RUBY')
535+
h = Hash.new(Mutex.new) # unshareable: default value is a Mutex
536+
assert_warning(/can not make shareable object.*from Hash default value/m) do
537+
Ractor.check_isolation { Ractor.make_shareable(h) }
538+
end
539+
# Outside check_isolation it still raises (with the reference chain).
540+
assert_raise(Ractor::Error) { Ractor.make_shareable(Hash.new(Mutex.new)) }
541+
RUBY
542+
end
543+
544+
def test_check_isolation_warns_for_shareable_proc
545+
assert_ractor(<<~'RUBY')
546+
foo = []
547+
assert_warning(/cannot make a shareable Proc because it can refer unshareable object/) do
548+
Ractor.check_isolation { Ractor.shareable_proc { foo } }
549+
end
550+
# Outside check_isolation it still raises.
551+
assert_raise(Ractor::IsolationError) do
552+
bar = []
553+
Ractor.shareable_proc { bar }
554+
end
555+
RUBY
556+
end
557+
558+
def test_check_isolation_warns_for_ractor_unsafe_method
559+
assert_ractor(<<~'RUBY')
560+
require 'etc'
561+
assert_warning(/ractor unsafe method called from not main ractor/) do
562+
Ractor.check_isolation { Etc.passwd }
563+
end
564+
RUBY
565+
end
566+
396567
def assert_make_shareable(obj)
397568
refute Ractor.shareable?(obj), "object was already shareable"
398569
Ractor.make_shareable(obj)

0 commit comments

Comments
 (0)