55#include "ruby/ractor.h"
66#include "ruby/re.h"
77#include "ruby/thread_native.h"
8+ #include "ruby_atomic.h"
89#include "vm_core.h"
910#include "vm_sync.h"
1011#include "ractor_core.h"
@@ -840,11 +841,12 @@ rb_ractor_main_setup(rb_vm_t *vm, rb_ractor_t *r, rb_thread_t *th)
840841}
841842
842843static VALUE
843- ractor_create (rb_execution_context_t * ec , VALUE self , VALUE loc , VALUE name , VALUE args , VALUE block )
844+ ractor_create0 (rb_execution_context_t * ec , VALUE self , VALUE loc , VALUE name , VALUE args , VALUE block , bool isolation_check )
844845{
845846 VALUE rv = ractor_alloc (self );
846847 rb_ractor_t * r = RACTOR_PTR (rv );
847848 ractor_init (r , name , loc );
849+ r -> isolation_check = isolation_check ;
848850
849851 r -> pub .id = ractor_next_id ();
850852 RUBY_DEBUG_LOG ("r:%u" , r -> pub .id );
@@ -863,6 +865,12 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL
863865 return rv ;
864866}
865867
868+ static VALUE
869+ ractor_create (rb_execution_context_t * ec , VALUE self , VALUE loc , VALUE name , VALUE args , VALUE block )
870+ {
871+ return ractor_create0 (ec , self , loc , name , args , block , false);
872+ }
873+
866874#if 0
867875static VALUE
868876ractor_create_func (VALUE klass , VALUE loc , VALUE name , VALUE args , rb_block_call_func_t func )
@@ -1850,6 +1858,12 @@ make_shareable_check_shareable(VALUE obj)
18501858 }
18511859 else if (!allow_frozen_shareable_p (obj )) {
18521860 if (!RB_TYPE_P (obj , T_DATA )) {
1861+ if (rb_ractor_isolation_check_p ()) {
1862+ rb_category_warn (RB_WARN_CATEGORY_RACTOR_ISOLATION ,
1863+ "can not make shareable object of class %+" PRIsVALUE ,
1864+ rb_class_of (obj ));
1865+ return traverse_stop ;
1866+ }
18531867 rb_raise (rb_eRactorError ,
18541868 "can not make shareable object for %+" PRIsVALUE , obj );
18551869 }
@@ -1859,14 +1873,26 @@ make_shareable_check_shareable(VALUE obj)
18591873 RB_OBJ_SET_SHAREABLE (obj );
18601874 return traverse_skip ;
18611875 }
1876+ else if (rb_ractor_isolation_check_p ()) {
1877+ rb_category_warn (RB_WARN_CATEGORY_RACTOR_ISOLATION ,
1878+ "can not make shareable object of class %+" PRIsVALUE
1879+ " because it refers unshareable objects" , rb_class_of (obj ));
1880+ return traverse_stop ;
1881+ }
18621882 else {
18631883 rb_raise (rb_eRactorError ,
18641884 "can not make shareable object for %+" PRIsVALUE " because it refers unshareable objects" , obj );
18651885 }
18661886 }
18671887 else if (rb_obj_is_proc (obj )) {
18681888 rb_proc_ractor_make_shareable (obj , Qundef );
1869- return traverse_cont ;
1889+ return rb_ractor_shareable_p (obj ) ? traverse_cont : traverse_stop ;
1890+ }
1891+ else if (rb_ractor_isolation_check_p ()) {
1892+ rb_category_warn (RB_WARN_CATEGORY_RACTOR_ISOLATION ,
1893+ "can not make shareable object of class %+" PRIsVALUE ,
1894+ rb_class_of (obj ));
1895+ return traverse_stop ;
18701896 }
18711897 else {
18721898 rb_raise (rb_eRactorError , "can not make shareable object for %+" PRIsVALUE , obj );
@@ -1930,9 +1956,10 @@ VALUE
19301956rb_ractor_ensure_shareable (VALUE obj , VALUE name )
19311957{
19321958 if (!rb_ractor_shareable_p (obj )) {
1933- VALUE message = rb_sprintf ("cannot assign unshareable object to %" PRIsVALUE ,
1934- name );
1935- rb_exc_raise (rb_exc_new_str (rb_eRactorIsolationError , message ));
1959+ rb_ractor_isolation_violation ("cannot assign unshareable object to %" PRIsVALUE , name );
1960+ // In check_isolation mode the violation only warned: return obj as-is
1961+ // so the caller can keep going. The caller's invariant ("this is now
1962+ // shareable") will be wrong, which is exactly the bug we want surfaced.
19361963 }
19371964 return obj ;
19381965}
@@ -1941,7 +1968,7 @@ void
19411968rb_ractor_ensure_main_ractor (const char * msg )
19421969{
19431970 if (!rb_ractor_main_p ()) {
1944- rb_raise ( rb_eRactorIsolationError , "%s" , msg );
1971+ rb_ractor_isolation_violation ( "%s" , msg );
19451972 }
19461973}
19471974
@@ -3783,13 +3810,11 @@ ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE
37833810static VALUE
37843811ractor_shareable_proc (rb_execution_context_t * ec , VALUE replace_self , bool is_lambda )
37853812{
3786- if (!rb_ractor_shareable_p (replace_self )) {
3787- rb_raise (rb_eRactorIsolationError , "self should be shareable: %" PRIsVALUE , replace_self );
3788- }
3789- else {
3790- VALUE proc = is_lambda ? rb_block_lambda () : rb_block_proc ();
3791- return rb_proc_ractor_make_shareable (rb_proc_dup (proc ), replace_self );
3813+ if (!rb_ractor_shareable_p (replace_self ) && !rb_ractor_isolation_check_p ()) {
3814+ rb_ractor_isolation_violation ("self should be shareable: %" PRIsVALUE , replace_self );
37923815 }
3816+ VALUE proc = is_lambda ? rb_block_lambda () : rb_block_proc ();
3817+ return rb_proc_ractor_make_shareable (rb_proc_dup (proc ), replace_self );
37933818}
37943819
37953820// Ractor#require
@@ -4001,4 +4026,76 @@ rb_ractor_autoload_load(VALUE module, ID name)
40014026 }
40024027}
40034028
4029+ // =============================================================================
4030+ // Ractor.check_isolation { ... }
4031+ //
4032+ // A development/debugging mode: the block runs in a genuine non-main Ractor,
4033+ // without isolating its Proc or copying its arguments. Violations are
4034+ // downgraded from Ractor::IsolationError to :ractor_isolation category warnings
4035+ // so the program can keep running and report more than the first violation.
4036+ //
4037+ // As a side effect (matches Ractor semantics), the VM is switched into
4038+ // multi-ractor mode the first time check_isolation is enabled. Multi-ractor
4039+ // mode cannot be turned off again, so the VM keeps paying that overhead for
4040+ // the rest of the process lifetime.
4041+ // =============================================================================
4042+
4043+ bool
4044+ rb_ractor_isolation_check_p (void )
4045+ {
4046+ rb_execution_context_t * ec = rb_current_ec_noinline ();
4047+ if (!ec ) return false;
4048+ rb_ractor_t * r = rb_ec_ractor_ptr (ec );
4049+ return r && r -> isolation_check ;
4050+ }
4051+
4052+ void
4053+ rb_ractor_isolation_violation_str (VALUE message )
4054+ {
4055+ if (rb_ractor_isolation_check_p ()) {
4056+ rb_category_warn (RB_WARN_CATEGORY_RACTOR_ISOLATION , "%s" , StringValueCStr (message ));
4057+ return ;
4058+ }
4059+
4060+ rb_exc_raise (rb_exc_new_str (rb_eRactorIsolationError , message ));
4061+ }
4062+
4063+ void
4064+ rb_ractor_isolation_violation (const char * fmt , ...)
4065+ {
4066+ va_list args ;
4067+ va_start (args , fmt );
4068+ VALUE message = rb_vsprintf (fmt , args );
4069+ va_end (args );
4070+
4071+ rb_ractor_isolation_violation_str (message );
4072+ }
4073+
4074+ /* Set during native-thread scheduler initialization; see thread_sched.c. */
4075+ extern int ruby_ractor_exclusive_enabled ;
4076+
4077+ static rb_atomic_t ractor_check_isolation_advisory_emitted ;
4078+
4079+ /* Return true to exactly one caller when nonexclusive mode needs its advisory.
4080+ * This state cannot live on Ractor itself: setting a class/module ivar from an
4081+ * ordinary non-main Ractor is itself an isolation violation. */
4082+ static VALUE
4083+ ractor_check_isolation_warn_p (rb_execution_context_t * ec , VALUE self )
4084+ {
4085+ if (ruby_ractor_exclusive_enabled ) return Qfalse ;
4086+ return RBOOL (ATOMIC_EXCHANGE (ractor_check_isolation_advisory_emitted , 1 ) == 0 );
4087+ }
4088+
4089+ static VALUE
4090+ ractor_check_isolation_p (rb_execution_context_t * ec , VALUE self )
4091+ {
4092+ return RBOOL (rb_ractor_isolation_check_p ());
4093+ }
4094+
4095+ static VALUE
4096+ ractor_check_isolation_create (rb_execution_context_t * ec , VALUE self , VALUE loc , VALUE name , VALUE args , VALUE block )
4097+ {
4098+ return ractor_create0 (ec , self , loc , name , args , block , true);
4099+ }
4100+
40044101#include "ractor.rbinc"
0 commit comments