@@ -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