Skip to content

Commit 424c580

Browse files
committed
Expand to shareable_proc and shareable_lambda
1 parent 9dacbfd commit 424c580

7 files changed

Lines changed: 132 additions & 22 deletions

File tree

error.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,8 +228,8 @@ rb_warning_category_enabled_p(rb_warning_category_t category)
228228
* +:ractor_isolation+ ::
229229
* Ractor isolation violations reported by Ractor.check_isolation
230230
* (downgraded from Ractor::IsolationError exceptions to warnings), and
231-
* FrozenError warnings reported for objects passed to Ractor.make_shareable
232-
* while Ractor.warn_frozen_error is enabled.
231+
* FrozenError warnings reported for objects that would be frozen for Ractor
232+
* shareability while Ractor.warn_frozen_error is enabled.
233233
*/
234234

235235
static VALUE

include/ruby/internal/intern/error.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,16 +202,16 @@ RBIMPL_ATTR_NORETURN()
202202
void rb_error_frozen_object(VALUE what);
203203

204204
/**
205-
* True once Ractor.warn_frozen_error has marked any objects whose would-be
205+
* True once Ractor.warn_frozen_error has recorded any objects whose would-be
206206
* FrozenError should be reported as a warning instead.
207207
*
208208
* @internal
209209
*/
210210
RUBY_EXTERN bool ruby_ractor_warn_frozen_error_objects_enabled;
211211

212212
/**
213-
* Emits a warning and returns true if +obj+ was recorded by Ractor.make_shareable
214-
* while Ractor.warn_frozen_error was enabled.
213+
* Emits a warning and returns true if +obj+ was recorded for Ractor
214+
* shareability while Ractor.warn_frozen_error was enabled.
215215
*
216216
* @internal
217217
*/

ractor.c

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1270,13 +1270,13 @@ rb_ractor_warn_frozen_error_warn(VALUE obj)
12701270
}
12711271

12721272
rb_category_warn(RB_WARN_CATEGORY_RACTOR_ISOLATION,
1273-
"would raise FrozenError: can't modify object passed to Ractor.make_shareable with Ractor.warn_frozen_error=true: %"PRIsVALUE,
1273+
"would raise FrozenError: can't modify object that would be frozen for Ractor shareability with Ractor.warn_frozen_error=true: %"PRIsVALUE,
12741274
rb_obj_class(obj));
12751275
return true;
12761276
}
12771277

1278-
static void
1279-
ractor_warn_frozen_error_mark(VALUE obj)
1278+
void
1279+
rb_ractor_warn_frozen_error_mark(VALUE obj)
12801280
{
12811281
if (RB_SPECIAL_CONST_P(obj)) {
12821282
return;
@@ -1714,7 +1714,7 @@ make_shareable_warn_check_shareable(VALUE obj, struct obj_traverse_data *data)
17141714

17151715
if (type->flags & RUBY_TYPED_FROZEN_SHAREABLE_NO_REC) {
17161716
if (obj_refer_only_shareables_p(obj)) {
1717-
ractor_warn_frozen_error_mark(obj);
1717+
rb_ractor_warn_frozen_error_mark(obj);
17181718
return traverse_skip;
17191719
}
17201720
else {
@@ -1754,7 +1754,7 @@ make_shareable_warn_check_shareable(VALUE obj, struct obj_traverse_data *data)
17541754
static enum obj_traverse_iterator_result
17551755
mark_warn_shareable(VALUE obj)
17561756
{
1757-
ractor_warn_frozen_error_mark(obj);
1757+
rb_ractor_warn_frozen_error_mark(obj);
17581758
return traverse_cont;
17591759
}
17601760

@@ -2699,9 +2699,14 @@ static VALUE
26992699
ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_lambda)
27002700
{
27012701
if (!rb_ractor_shareable_p(replace_self)) {
2702-
// In check_isolation mode this only warns; fall through and try to
2703-
// make the proc shareable anyway so the sweep can keep going.
2704-
rb_ractor_isolation_violation("self should be shareable: %" PRIsVALUE, replace_self);
2702+
if (ruby_ractor_warn_frozen_error) {
2703+
rb_ractor_make_shareable(replace_self);
2704+
}
2705+
else {
2706+
// In check_isolation mode this only warns; fall through and try to
2707+
// make the proc shareable anyway so the sweep can keep going.
2708+
rb_ractor_isolation_violation("self should be shareable: %" PRIsVALUE, replace_self);
2709+
}
27052710
}
27062711
VALUE proc = is_lambda ? rb_block_lambda() : rb_block_proc();
27072712
return rb_proc_ractor_make_shareable(rb_proc_dup(proc), replace_self);

ractor.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,11 @@ def unmonitor port
735735
# Ractor.shareable_proc(self: self){}
736736
# #=> self should be shareable: main (Ractor::IsolationError)
737737
#
738+
# If +Ractor.warn_frozen_error+ is enabled, the returned Proc (and any
739+
# unshareable replacement +self+) is recorded for mutation warnings instead
740+
# of being frozen or made actually shareable. This mode is intended only for
741+
# development sweeps.
742+
#
738743
def self.shareable_proc self: nil
739744
Primitive.attr! :use_block
740745

@@ -748,6 +753,7 @@ def self.shareable_proc self: nil
748753
# Ractor.shareable_lambda(self: nil){} -> shareable lambda
749754
#
750755
# Same as Ractor.shareable_proc, but returns a lambda Proc.
756+
# +Ractor.warn_frozen_error+ has the same development-mode behavior here.
751757
#
752758
def self.shareable_lambda self: nil
753759
Primitive.attr! :use_block

ractor_core.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,24 @@ st_table *rb_ractor_targeted_hooks(rb_ractor_t *cr);
167167
* gate site -- they just pay one extra LIKELY-false byte load. */
168168
RUBY_EXTERN bool ruby_ractor_isolation_check_enabled;
169169

170+
/* Global opt-in used by Ractor.make_shareable, Ractor.shareable_proc, and
171+
* Ractor.shareable_lambda to record would-be frozen objects and warn on later
172+
* mutation instead of freezing them. */
173+
RUBY_EXTERN bool ruby_ractor_warn_frozen_error;
174+
170175
/* True if the current thread has Ractor.check_isolation enabled.
171176
* Hot-path callers should NOT call this directly; go through
172177
* rb_ractor_isolation_check_active(). */
173178
bool rb_thread_ractor_isolation_check_p(void);
174179

175-
/* True if obj was made warning-shareable by Ractor.make_shareable while
176-
* Ractor.warn_frozen_error was enabled. */
180+
/* True if obj was recorded by Ractor.make_shareable, Ractor.shareable_proc, or
181+
* Ractor.shareable_lambda while Ractor.warn_frozen_error was enabled. */
177182
bool rb_ractor_warn_frozen_error_marked_p(VALUE obj);
178183

184+
/* Record obj so future mutation attempts emit :ractor_isolation warnings
185+
* instead of raising FrozenError. */
186+
void rb_ractor_warn_frozen_error_mark(VALUE obj);
187+
179188
/* Report a Ractor isolation violation:
180189
* - if Ractor.check_isolation is active on the current thread, emit a
181190
* :ractor_isolation category warning and return;

test/ruby/test_ractor.rb

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,77 @@ def test_warn_frozen_error_warns_on_instance_variable_writes
637637
RUBY
638638
end
639639

640+
def test_warn_frozen_error_marks_shareable_proc_without_freezing
641+
assert_ractor(<<~'RUBY')
642+
old = Ractor.warn_frozen_error
643+
begin
644+
Ractor.warn_frozen_error = true
645+
pr = Ractor.shareable_proc { :ok }
646+
647+
assert_equal false, Ractor.shareable?(pr)
648+
assert_equal false, pr.frozen?
649+
assert_equal :ok, pr.call
650+
651+
assert_warning(/would raise FrozenError.*Proc/) do
652+
pr.instance_variable_set(:@mutated, true)
653+
end
654+
assert_equal true, pr.instance_variable_get(:@mutated)
655+
ensure
656+
Ractor.warn_frozen_error = old
657+
end
658+
RUBY
659+
end
660+
661+
def test_warn_frozen_error_marks_shareable_lambda_without_freezing
662+
assert_ractor(<<~'RUBY')
663+
old = Ractor.warn_frozen_error
664+
begin
665+
Ractor.warn_frozen_error = true
666+
pr = Ractor.shareable_lambda { :ok }
667+
668+
assert_equal false, Ractor.shareable?(pr)
669+
assert_equal false, pr.frozen?
670+
assert_equal true, pr.lambda?
671+
assert_equal :ok, pr.call
672+
673+
assert_warning(/would raise FrozenError.*Proc/) do
674+
pr.instance_variable_set(:@mutated, true)
675+
end
676+
assert_equal true, pr.instance_variable_get(:@mutated)
677+
ensure
678+
Ractor.warn_frozen_error = old
679+
end
680+
RUBY
681+
end
682+
683+
def test_warn_frozen_error_marks_shareable_proc_self_without_freezing
684+
assert_ractor(<<~'RUBY')
685+
old = Ractor.warn_frozen_error
686+
begin
687+
Ractor.warn_frozen_error = true
688+
replacement_self = Object.new
689+
nested = []
690+
replacement_self.instance_variable_set(:@nested, nested)
691+
pr = Ractor.shareable_proc(self: replacement_self) { self }
692+
693+
assert_same replacement_self, pr.call
694+
assert_equal false, replacement_self.frozen?
695+
assert_equal false, nested.frozen?
696+
697+
assert_warning(/would raise FrozenError.*Object/) do
698+
replacement_self.instance_variable_set(:@mutated, true)
699+
end
700+
assert_equal true, replacement_self.instance_variable_get(:@mutated)
701+
assert_warning(/would raise FrozenError.*Array/) do
702+
nested << :mutated
703+
end
704+
assert_equal [:mutated], nested
705+
ensure
706+
Ractor.warn_frozen_error = old
707+
end
708+
RUBY
709+
end
710+
640711
def test_warn_frozen_error_off_uses_normal_make_shareable_freezing
641712
assert_ractor(<<~'RUBY')
642713
old = Ractor.warn_frozen_error
@@ -646,6 +717,10 @@ def test_warn_frozen_error_off_uses_normal_make_shareable_freezing
646717
Ractor.make_shareable(ary)
647718
assert_equal true, ary.frozen?
648719
assert_raise(FrozenError) { ary << :mutated }
720+
721+
pr = Ractor.shareable_proc { :ok }
722+
assert_equal true, pr.frozen?
723+
assert_raise(FrozenError) { pr.instance_variable_set(:@mutated, true) }
649724
ensure
650725
Ractor.warn_frozen_error = old
651726
end

vm.c

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1609,9 +1609,14 @@ rb_proc_ractor_make_shareable_continue(VALUE self, VALUE replace_self, VALUE *ch
16091609
if (!RB_SPECIAL_CONST_P(block_self) &&
16101610
!RB_OBJ_SHAREABLE_P(block_self)) {
16111611
if (!rb_ractor_shareable_p_continue(block_self, chain)) {
1612-
rb_ractor_error_chain_append(chain, "\n from block's self (an instance of %"PRIsVALUE")",
1613-
rb_class_real(CLASS_OF(block_self)));
1614-
return false;
1612+
if (ruby_ractor_warn_frozen_error) {
1613+
rb_ractor_make_shareable(block_self);
1614+
}
1615+
else {
1616+
rb_ractor_error_chain_append(chain, "\n from block's self (an instance of %"PRIsVALUE")",
1617+
rb_class_real(CLASS_OF(block_self)));
1618+
return false;
1619+
}
16151620
}
16161621
}
16171622

@@ -1631,13 +1636,23 @@ rb_proc_ractor_make_shareable_continue(VALUE self, VALUE replace_self, VALUE *ch
16311636

16321637
VALUE proc_self = vm_block_self(block);
16331638
if (!rb_ractor_shareable_p_continue(proc_self, chain)) {
1634-
rb_ractor_error_chain_append(chain, "\n from proc's self (an instance of %"PRIsVALUE")",
1635-
rb_class_real(CLASS_OF(proc_self)));
1636-
return false;
1639+
if (ruby_ractor_warn_frozen_error) {
1640+
rb_ractor_make_shareable(proc_self);
1641+
}
1642+
else {
1643+
rb_ractor_error_chain_append(chain, "\n from proc's self (an instance of %"PRIsVALUE")",
1644+
rb_class_real(CLASS_OF(proc_self)));
1645+
return false;
1646+
}
16371647
}
16381648
}
16391649

1640-
RB_OBJ_SET_FROZEN_SHAREABLE(self);
1650+
if (ruby_ractor_warn_frozen_error) {
1651+
rb_ractor_warn_frozen_error_mark(self);
1652+
}
1653+
else {
1654+
RB_OBJ_SET_FROZEN_SHAREABLE(self);
1655+
}
16411656
return true;
16421657
}
16431658

0 commit comments

Comments
 (0)