diff options
| author | Koichi Sasada <ko1@atdot.net> | 2025-07-17 15:38:54 +0900 |
|---|---|---|
| committer | Koichi Sasada <ko1@atdot.net> | 2025-09-24 03:59:03 +0900 |
| commit | 55b1ba3bf276ba82173bd961fb8e0f08bf4182a6 (patch) | |
| tree | 9da723c4412af61ef221993fa021da97df9be1b0 | |
| parent | c05ea920cef5991ca6163d12a436a61219a234a6 (diff) | |
Ractor.shareable_proc
call-seq:
Ractor.sharable_proc(self: nil){} -> sharable proc
It returns shareable Proc object. The Proc object is
shareable and the self in a block will be replaced with
the value passed via `self:` keyword.
In a shareable Proc, the outer variables should
* (1) refer shareable objects
* (2) be not be overwritten
```ruby
a = 42
Ractor.shareable_proc{ p a }
#=> OK
b = 43
Ractor.shareable_proc{ p b; b = 44 }
#=> Ractor::IsolationError because 'b' is reassigned in the block.
c = 44
Ractor.shareable_proc{ p c }
#=> Ractor::IsolationError because 'c' will be reassigned outside of the block.
c = 45
d = 45
d = 46 if cond
Ractor.shareable_proc{ p d }
#=> Ractor::IsolationError because 'd' was reassigned outside of the block.
```
The last `d`'s case can be relaxed in a future version.
The above check will be done in a static analysis at compile time,
so the reflection feature such as `Binding#local_varaible_set`
can not be detected.
```ruby
e = 42
shpr = Ractor.shareable_proc{ p e } #=> OK
binding.local_variable_set(:e, 43)
shpr.call #=> 42 (returns captured timing value)
```
Ractor.sharaeble_lambda is also introduced.
[Feature #21550]
[Feature #21557]
| -rw-r--r-- | NEWS.md | 6 | ||||
| -rw-r--r-- | bootstraptest/test_ractor.rb | 163 | ||||
| -rw-r--r-- | compile.c | 91 | ||||
| -rw-r--r-- | iseq.c | 5 | ||||
| -rw-r--r-- | prism_compile.c | 3 | ||||
| -rw-r--r-- | ractor.c | 16 | ||||
| -rw-r--r-- | ractor.rb | 43 | ||||
| -rw-r--r-- | test/ruby/test_iseq.rb | 23 | ||||
| -rw-r--r-- | test/ruby/test_ractor.rb | 30 | ||||
| -rw-r--r-- | thread.c | 2 | ||||
| -rw-r--r-- | vm.c | 35 | ||||
| -rw-r--r-- | vm_core.h | 10 |
12 files changed, 254 insertions, 173 deletions
@@ -117,6 +117,10 @@ Note: We're only listing outstanding class updates. * `Ractor#close_incoming` and `Ractor#close_outgoing` were removed. + * `Ractor.sharealbe_proc` and `Ractor.shareable_lambda` is introduced + to make shareable Proc or lambda. + [[Feature #21550]], [[Feature #21557]] + * `Set` * `Set` is now a core class, instead of an autoloaded stdlib class. @@ -316,3 +320,5 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 [Feature #21360]: https://bugs.ruby-lang.org/issues/21360 [Feature #21527]: https://bugs.ruby-lang.org/issues/21527 +[Feature #21550]: https://bugs.ruby-lang.org/issues/21550 +[Feature #21557]: https://bugs.ruby-lang.org/issues/21557 diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 74fee197f8..634a3e3e61 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -145,28 +145,47 @@ assert_equal '[:ok, :ok, :ok]', %q{ }.map(&:value) } +assert_equal "42", %q{ + a = 42 + Ractor.shareable_lambda{ a }.call +} + # Ractor.make_shareable issue for locals in proc [Bug #18023] assert_equal '[:a, :b, :c, :d, :e]', %q{ v1, v2, v3, v4, v5 = :a, :b, :c, :d, :e - closure = Ractor.current.instance_eval{ Proc.new { [v1, v2, v3, v4, v5] } } - - Ractor.make_shareable(closure).call + closure = Proc.new { [v1, v2, v3, v4, v5] } + Ractor.shareable_proc(&closure).call } -# Ractor.make_shareable issue for locals in proc [Bug #18023] -assert_equal '[:a, :b, :c, :d, :e, :f, :g]', %q{ - a = :a - closure = Ractor.current.instance_eval do - -> { - b, c, d = :b, :c, :d - -> { - e, f, g = :e, :f, :g - -> { [a, b, c, d, e, f, g] } - }.call - }.call +# Ractor::IsolationError cases +assert_equal '3', %q{ + ok = 0 + + begin + a = 1 + Ractor.shareable_proc{a} + a = 2 + rescue Ractor::IsolationError => e + ok += 1 end - Ractor.make_shareable(closure).call + begin + cond = false + a = 1 + a = 2 if cond + Ractor.shareable_proc{a} + rescue Ractor::IsolationError => e + ok += 1 + end + + begin + 1.times{|i| + i = 2 + Ractor.shareable_proc{i} + } + rescue Ractor::IsolationError => e + ok += 1 + end } ### @@ -967,7 +986,7 @@ assert_equal 'can not access non-shareable objects in constant C::CONST by non-m end RUBY -# Constant cache should care about non-sharable constants +# Constant cache should care about non-shareable constants assert_equal "can not access non-shareable objects in constant Object::STR by non-main Ractor.", <<~'RUBY', frozen_string_literal: false STR = "hello" def str; STR; end @@ -1137,41 +1156,17 @@ assert_equal 'true', %q{ [a.frozen?, a[0].frozen?] == [true, false] } -# Ractor.make_shareable(a_proc) makes a proc shareable. +# Ractor.make_shareable(a_proc) is not supported now. assert_equal 'true', %q{ - a = [1, [2, 3], {a: "4"}] - - pr = Ractor.current.instance_eval do - Proc.new do - a - end - end + pr = Proc.new{} - Ractor.make_shareable(a) # referred value should be shareable - Ractor.make_shareable(pr) - Ractor.shareable?(pr) -} - -# Ractor.make_shareable(a_proc) makes inner structure shareable and freezes it -assert_equal 'true,true,true,true', %q{ - class Proc - attr_reader :obj - def initialize - @obj = Object.new - end - end - - pr = Ractor.current.instance_eval do - Proc.new {} + begin + Ractor.make_shareable(pr) + rescue Ractor::Error + true + else + false end - - results = [] - Ractor.make_shareable(pr) - results << Ractor.shareable?(pr) - results << pr.frozen? - results << Ractor.shareable?(pr.obj) - results << pr.obj.frozen? - results.map(&:to_s).join(',') } # Ractor.shareable?(recursive_objects) @@ -1202,50 +1197,16 @@ assert_equal '[C, M]', %q{ Ractor.make_shareable(ary = [C, M]) } -# Ractor.make_shareable with curried proc checks isolation of original proc -assert_equal 'isolation error', %q{ - a = Object.new - orig = proc { a } - curried = orig.curry - - begin - Ractor.make_shareable(curried) - rescue Ractor::IsolationError - 'isolation error' - else - 'no error' - end -} - # define_method() can invoke different Ractor's proc if the proc is shareable. assert_equal '1', %q{ class C a = 1 - define_method "foo", Ractor.make_shareable(Proc.new{ a }) - a = 2 + define_method "foo", Ractor.shareable_proc{ a } end Ractor.new{ C.new.foo }.value } -# Ractor.make_shareable(a_proc) makes a proc shareable. -assert_equal 'can not make a Proc shareable because it accesses outer variables (a).', %q{ - a = b = nil - pr = Ractor.current.instance_eval do - Proc.new do - c = b # assign to a is okay because c is block local variable - # reading b is okay - a = b # assign to a is not allowed #=> Ractor::Error - end - end - - begin - Ractor.make_shareable(pr) - rescue => e - e.message - end -} - # Ractor.make_shareable(obj, copy: true) makes copied shareable object. assert_equal '[false, false, true, true]', %q{ r = [] @@ -1471,42 +1432,6 @@ assert_equal "ok", %q{ "ok" } if !yjit_enabled? && ENV['GITHUB_WORKFLOW'] != 'ModGC' # flaky -assert_equal "ok", %q{ - def foo(*); ->{ super }; end - begin - Ractor.make_shareable(foo) - rescue Ractor::IsolationError - "ok" - end -} - -assert_equal "ok", %q{ - def foo(**); ->{ super }; end - begin - Ractor.make_shareable(foo) - rescue Ractor::IsolationError - "ok" - end -} - -assert_equal "ok", %q{ - def foo(...); ->{ super }; end - begin - Ractor.make_shareable(foo) - rescue Ractor::IsolationError - "ok" - end -} - -assert_equal "ok", %q{ - def foo((x), (y)); ->{ super }; end - begin - Ractor.make_shareable(foo([], [])) - rescue Ractor::IsolationError - "ok" - end -} - # check method cache invalidation assert_equal "ok", %q{ module M @@ -494,6 +494,7 @@ static int iseq_set_arguments(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, const static int iseq_set_sequence(rb_iseq_t *iseq, LINK_ANCHOR *const anchor); static int iseq_set_exception_table(rb_iseq_t *iseq); static int iseq_set_optargs_table(rb_iseq_t *iseq); +static int iseq_set_parameters_lvar_state(const rb_iseq_t *iseq); static int compile_defined_expr(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, VALUE needstr, bool ignore); static int compile_hash(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *node, int method_call_keywords, int popped); @@ -876,6 +877,7 @@ rb_iseq_compile_node(rb_iseq_t *iseq, const NODE *node) /* iseq type of top, method, class, block */ iseq_set_local_table(iseq, RNODE_SCOPE(node)->nd_tbl, (NODE *)RNODE_SCOPE(node)->nd_args); iseq_set_arguments(iseq, ret, (NODE *)RNODE_SCOPE(node)->nd_args); + iseq_set_parameters_lvar_state(iseq); switch (ISEQ_BODY(iseq)->type) { case ISEQ_TYPE_BLOCK: @@ -1715,6 +1717,7 @@ iseq_set_exception_local_table(rb_iseq_t *iseq) { ISEQ_BODY(iseq)->local_table_size = numberof(rb_iseq_shared_exc_local_tbl); ISEQ_BODY(iseq)->local_table = rb_iseq_shared_exc_local_tbl; + ISEQ_BODY(iseq)->lvar_states = NULL; // $! is read-only, so don't need lvar_states return COMPILE_OK; } @@ -1862,6 +1865,46 @@ iseq_lvar_id(const rb_iseq_t *iseq, int idx, int level) } static void +update_lvar_state(const rb_iseq_t *iseq, int level, int idx) +{ + for (int i=0; i<level; i++) { + iseq = ISEQ_BODY(iseq)->parent_iseq; + } + + enum lvar_state *states = ISEQ_BODY(iseq)->lvar_states; + int table_idx = ISEQ_BODY(iseq)->local_table_size - idx; + switch (states[table_idx]) { + case lvar_uninitialized: + states[table_idx] = lvar_initialized; + break; + case lvar_initialized: + states[table_idx] = lvar_reassigned; + break; + case lvar_reassigned: + /* nothing */ + break; + default: + rb_bug("unreachable"); + } +} + +static int +iseq_set_parameters_lvar_state(const rb_iseq_t *iseq) +{ + for (unsigned int i=0; i<ISEQ_BODY(iseq)->param.size; i++) { + ISEQ_BODY(iseq)->lvar_states[i] = lvar_initialized; + } + + int lead_num = ISEQ_BODY(iseq)->param.lead_num; + int opt_num = ISEQ_BODY(iseq)->param.opt_num; + for (int i=0; i<opt_num; i++) { + ISEQ_BODY(iseq)->lvar_states[lead_num + i] = lvar_uninitialized; + } + + return COMPILE_OK; +} + +static void iseq_add_getlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, const NODE *const line_node, int idx, int level) { if (iseq_local_block_param_p(iseq, idx, level)) { @@ -1882,6 +1925,7 @@ iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, const NODE *const lin else { ADD_INSN2(seq, line_node, setlocal, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level)); } + update_lvar_state(iseq, level, idx); if (level > 0) access_outer_variables(iseq, level, iseq_lvar_id(iseq, idx, level), Qtrue); } @@ -2212,6 +2256,14 @@ iseq_set_local_table(rb_iseq_t *iseq, const rb_ast_id_table_t *tbl, const NODE * ID *ids = ALLOC_N(ID, size); MEMCPY(ids, tbl->ids + offset, ID, size); ISEQ_BODY(iseq)->local_table = ids; + + enum lvar_state *states = ALLOC_N(enum lvar_state, size); + // fprintf(stderr, "iseq:%p states:%p size:%d\n", iseq, states, (int)size); + for (unsigned int i=0; i<size; i++) { + states[i] = lvar_uninitialized; + // fprintf(stderr, "id:%s\n", rb_id2name(ISEQ_BODY(iseq)->local_table[i])); + } + ISEQ_BODY(iseq)->lvar_states = states; } ISEQ_BODY(iseq)->local_table_size = size; @@ -12379,7 +12431,7 @@ typedef uint32_t ibf_offset_t; #define IBF_MAJOR_VERSION ISEQ_MAJOR_VERSION #ifdef RUBY_DEVEL -#define IBF_DEVEL_VERSION 4 +#define IBF_DEVEL_VERSION 5 #define IBF_MINOR_VERSION (ISEQ_MINOR_VERSION * 10000 + IBF_DEVEL_VERSION) #else #define IBF_MINOR_VERSION ISEQ_MINOR_VERSION @@ -13190,7 +13242,7 @@ ibf_dump_local_table(struct ibf_dump *dump, const rb_iseq_t *iseq) return ibf_dump_write(dump, table, sizeof(ID) * size); } -static ID * +static const ID * ibf_load_local_table(const struct ibf_load *load, ibf_offset_t local_table_offset, int size) { if (size > 0) { @@ -13200,7 +13252,14 @@ ibf_load_local_table(const struct ibf_load *load, ibf_offset_t local_table_offse for (i=0; i<size; i++) { table[i] = ibf_load_id(load, table[i]); } - return table; + + if (size == 1 && table[0] == idERROR_INFO) { + xfree(table); + return rb_iseq_shared_exc_local_tbl; + } + else { + return table; + } } else { return NULL; @@ -13208,6 +13267,28 @@ ibf_load_local_table(const struct ibf_load *load, ibf_offset_t local_table_offse } static ibf_offset_t +ibf_dump_lvar_states(struct ibf_dump *dump, const rb_iseq_t *iseq) +{ + const struct rb_iseq_constant_body *const body = ISEQ_BODY(iseq); + const int size = body->local_table_size; + IBF_W_ALIGN(enum lvar_state); + return ibf_dump_write(dump, body->lvar_states, body->lvar_states ? size : 0); +} + +static enum lvar_state * +ibf_load_lvar_states(const struct ibf_load *load, ibf_offset_t lvar_states_offset, int size, const ID *local_table) +{ + if (local_table == rb_iseq_shared_exc_local_tbl || + size <= 0) { + return NULL; + } + else { + enum lvar_state *states = IBF_R(lvar_states_offset, enum lvar_state, size); + return states; + } +} + +static ibf_offset_t ibf_dump_catch_table(struct ibf_dump *dump, const rb_iseq_t *iseq) { const struct iseq_catch_table *table = ISEQ_BODY(iseq)->catch_table; @@ -13480,6 +13561,7 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) ruby_xfree(positions); const ibf_offset_t local_table_offset = ibf_dump_local_table(dump, iseq); + const ibf_offset_t lvar_states_offset = ibf_dump_lvar_states(dump, iseq); const unsigned int catch_table_size = body->catch_table ? body->catch_table->size : 0; const ibf_offset_t catch_table_offset = ibf_dump_catch_table(dump, iseq); const int parent_iseq_index = ibf_dump_iseq(dump, ISEQ_BODY(iseq)->parent_iseq); @@ -13547,6 +13629,7 @@ ibf_dump_iseq_each(struct ibf_dump *dump, const rb_iseq_t *iseq) ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(insns_info_positions_offset)); ibf_dump_write_small_value(dump, body->insns_info.size); ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(local_table_offset)); + ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(lvar_states_offset)); ibf_dump_write_small_value(dump, catch_table_size); ibf_dump_write_small_value(dump, IBF_BODY_OFFSET(catch_table_offset)); ibf_dump_write_small_value(dump, parent_iseq_index); @@ -13658,6 +13741,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) const ibf_offset_t insns_info_positions_offset = (ibf_offset_t)IBF_BODY_OFFSET(ibf_load_small_value(load, &reading_pos)); const unsigned int insns_info_size = (unsigned int)ibf_load_small_value(load, &reading_pos); const ibf_offset_t local_table_offset = (ibf_offset_t)IBF_BODY_OFFSET(ibf_load_small_value(load, &reading_pos)); + const ibf_offset_t lvar_states_offset = (ibf_offset_t)IBF_BODY_OFFSET(ibf_load_small_value(load, &reading_pos)); const unsigned int catch_table_size = (unsigned int)ibf_load_small_value(load, &reading_pos); const ibf_offset_t catch_table_offset = (ibf_offset_t)IBF_BODY_OFFSET(ibf_load_small_value(load, &reading_pos)); const int parent_iseq_index = (int)ibf_load_small_value(load, &reading_pos); @@ -13774,6 +13858,7 @@ ibf_load_iseq_each(struct ibf_load *load, rb_iseq_t *iseq, ibf_offset_t offset) load_body->insns_info.body = ibf_load_insns_info_body(load, insns_info_body_offset, insns_info_size); load_body->insns_info.positions = ibf_load_insns_info_positions(load, insns_info_positions_offset, insns_info_size); load_body->local_table = ibf_load_local_table(load, local_table_offset, local_table_size); + load_body->lvar_states = ibf_load_lvar_states(load, lvar_states_offset, local_table_size, load_body->local_table); ibf_load_catch_table(load, catch_table_offset, catch_table_size, iseq); const rb_iseq_t *parent_iseq = ibf_load_iseq(load, (const rb_iseq_t *)(VALUE)parent_iseq_index); @@ -199,8 +199,11 @@ rb_iseq_free(const rb_iseq_t *iseq) } ruby_xfree((void *)body->param.keyword); } - if (LIKELY(body->local_table != rb_iseq_shared_exc_local_tbl)) + if (LIKELY(body->local_table != rb_iseq_shared_exc_local_tbl)) { ruby_xfree((void *)body->local_table); + } + ruby_xfree((void *)body->lvar_states); + compile_data_free(ISEQ_COMPILE_DATA(iseq)); if (body->outer_variables) rb_id_table_free(body->outer_variables); ruby_xfree(body); diff --git a/prism_compile.c b/prism_compile.c index 578e6f240f..cd8287c762 100644 --- a/prism_compile.c +++ b/prism_compile.c @@ -102,6 +102,7 @@ pm_iseq_add_setlocal(rb_iseq_t *iseq, LINK_ANCHOR *const seq, int line, int node else { ADD_ELEM(seq, (LINK_ELEMENT *) new_insn_body(iseq, line, node_id, BIN(setlocal), 2, INT2FIX((idx) + VM_ENV_DATA_SIZE - 1), INT2FIX(level))); } + update_lvar_state(iseq, level, idx); if (level > 0) access_outer_variables(iseq, level, iseq_lvar_id(iseq, idx, level), Qtrue); } @@ -6796,6 +6797,8 @@ pm_compile_scope_node(rb_iseq_t *iseq, pm_scope_node_t *scope_node, const pm_nod // FIXME: args? iseq_set_local_table(iseq, local_table_for_iseq, 0); + iseq_set_parameters_lvar_state(iseq); + scope_node->local_table_for_iseq_size = local_table_for_iseq->size; if (keyword != NULL) { @@ -1374,7 +1374,7 @@ make_shareable_check_shareable(VALUE obj) } else if (!allow_frozen_shareable_p(obj)) { if (rb_obj_is_proc(obj)) { - rb_proc_ractor_make_shareable(obj); + rb_proc_ractor_make_shareable(obj, Qundef); return traverse_cont; } else { @@ -2273,6 +2273,20 @@ ractor_local_value_store_if_absent(rb_execution_context_t *ec, VALUE self, VALUE return rb_mutex_synchronize(cr->local_storage_store_lock, ractor_local_value_store_i, (VALUE)&data); } +// sharable_proc + +static VALUE +ractor_shareable_proc(rb_execution_context_t *ec, VALUE replace_self, bool is_lambda) +{ + if (!rb_ractor_shareable_p(replace_self)) { + rb_raise(rb_eRactorIsolationError, "self should be shareable: %" PRIsVALUE, replace_self); + } + else { + VALUE proc = is_lambda ? rb_block_lambda() : rb_block_proc(); + return rb_proc_ractor_make_shareable(proc, replace_self); + } +} + // Ractor#require struct cross_ractor_require { @@ -1,5 +1,3 @@ -# \Ractor is an Actor-model abstraction for Ruby that provides thread-safe parallel execution. -# # Ractor.new makes a new \Ractor, which can run in parallel. # # # The simplest ractor @@ -606,6 +604,47 @@ class Ractor __builtin_ractor_unmonitor(port) end + # + # call-seq: + # Ractor.sharable_proc(self: nil){} -> sharable proc + # + # It returns shareable Proc object. The Proc object is + # shareable and the self in a block will be replaced with + # the value passed via `self:` keyword. + # + # In a shareable Proc, you can not access to the outer variables. + # + # a = 42 + # Ractor.shareable_proc{ p a } + # #=> can not isolate a Proc because it accesses outer variables (a). (ArgumentError) + # + # The `self` should be a sharable object + # + # Ractor.shareable_proc(self: self){} + # #=> self should be shareable: main (Ractor::IsolationError) + # + def self.shareable_proc self: nil + Primitive.attr! :use_block + + __builtin_cexpr!(%Q{ + ractor_shareable_proc(ec, *LOCAL_PTR(self), false) + }) + end + + # + # call-seq: + # Ractor.sharable_proc{} -> sharable proc + # + # Same as Ractor.sharable_proc, but returns lambda proc. + # + def self.shareable_lambda self: nil + Primitive.attr! :use_block + + __builtin_cexpr!(%Q{ + ractor_shareable_proc(ec, *LOCAL_PTR(self), true) + }) + end + # \Port objects transmit messages between Ractors. class Port # diff --git a/test/ruby/test_iseq.rb b/test/ruby/test_iseq.rb index 45223c89da..fa716787fe 100644 --- a/test/ruby/test_iseq.rb +++ b/test/ruby/test_iseq.rb @@ -139,8 +139,7 @@ class TestISeq < Test::Unit::TestCase def test_lambda_with_ractor_roundtrip iseq = compile(<<~EOF, __LINE__+1) x = 42 - y = nil.instance_eval{ lambda { x } } - Ractor.make_shareable(y) + y = Ractor.shareable_lambda{x} y.call EOF assert_equal(42, ISeq.load_from_binary(iseq_to_binary(iseq)).eval) @@ -158,22 +157,18 @@ class TestISeq < Test::Unit::TestCase def test_ractor_unshareable_outer_variable name = "\u{2603 26a1}" - y = nil.instance_eval do - eval("proc {#{name} = nil; proc {|x| #{name} = x}}").call - end assert_raise_with_message(ArgumentError, /\(#{name}\)/) do - Ractor.make_shareable(y) - end - y = nil.instance_eval do - eval("proc {#{name} = []; proc {|x| #{name}}}").call + eval("#{name} = nil; Ractor.shareable_proc{#{name} = nil}") end - assert_raise_with_message(Ractor::IsolationError, /'#{name}'/) do - Ractor.make_shareable(y) + + assert_raise_with_message(Ractor::IsolationError, /\'#{name}\'/) do + eval("#{name} = []; Ractor.shareable_proc{#{name}}") end + obj = Object.new - def obj.foo(*) nil.instance_eval{ ->{super} } end - assert_raise_with_message(Ractor::IsolationError, /refer unshareable object \[\] from variable '\*'/) do - Ractor.make_shareable(obj.foo(*[])) + def obj.foo(*) Ractor.shareable_proc{super} end + assert_raise_with_message(Ractor::IsolationError, /cannot make a shareable Proc because it can refer unshareable object \[\]/) do + obj.foo(*[]) end end diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 70a2ca4bfb..c4154cd263 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -3,38 +3,19 @@ require 'test/unit' class TestRactor < Test::Unit::TestCase def test_shareability_of_iseq_proc - y = nil.instance_eval do + assert_raise Ractor::IsolationError do foo = [] - proc { foo } + Ractor.shareable_proc{ foo } end - assert_unshareable(y, /unshareable object \[\] from variable 'foo'/) - - y = [].instance_eval { proc { self } } - assert_unshareable(y, /Proc's self is not shareable/) - - y = [].freeze.instance_eval { proc { self } } - assert_make_shareable(y) - end - - def test_shareability_of_curried_proc - x = nil.instance_eval do - foo = [] - proc { foo }.curry - end - assert_unshareable(x, /unshareable object \[\] from variable 'foo'/) - - x = nil.instance_eval do - foo = 123 - proc { foo }.curry - end - assert_make_shareable(x) end def test_shareability_of_method_proc + # TODO: fix with Ractor.shareable_proc/lambda +=begin str = +"" x = str.instance_exec { proc { to_s } } - assert_unshareable(x, /Proc's self is not shareable/) + assert_unshareable(x, /Proc\'s self is not shareable/) x = str.instance_exec { method(:to_s) } assert_unshareable(x, "can not make shareable object for #<Method: String#to_s()>", exception: Ractor::Error) @@ -58,6 +39,7 @@ class TestRactor < Test::Unit::TestCase x = str.instance_exec { method(:itself).to_proc } assert_unshareable(x, "can not make shareable object for #<Method: String(Kernel)#itself()>", exception: Ractor::Error) +=end end def test_shareability_error_uses_inspect @@ -841,7 +841,7 @@ thread_create_core(VALUE thval, struct thread_create_params *params) th->invoke_type = thread_invoke_type_ractor_proc; th->ractor = params->g; th->ractor->threads.main = th; - th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc); + th->invoke_arg.proc.proc = rb_proc_isolate_bang(params->proc, Qnil); th->invoke_arg.proc.args = INT2FIX(RARRAY_LENINT(params->args)); th->invoke_arg.proc.kw_splat = rb_keyword_given_p(); rb_ractor_send_parameters(ec, params->g, params->args); @@ -1325,12 +1325,22 @@ env_copy(const VALUE *src_ep, VALUE read_only_variables) for (int i=RARRAY_LENINT(read_only_variables)-1; i>=0; i--) { ID id = NUM2ID(RARRAY_AREF(read_only_variables, i)); - for (unsigned int j=0; j<ISEQ_BODY(src_env->iseq)->local_table_size; j++) { - if (id == ISEQ_BODY(src_env->iseq)->local_table[j]) { + const struct rb_iseq_constant_body *body = ISEQ_BODY(src_env->iseq); + for (unsigned int j=0; j<body->local_table_size; j++) { + if (id == body->local_table[j]) { + // check reassignment + if (body->lvar_states[j] == lvar_reassigned) { + VALUE name = rb_id2str(id); + VALUE msg = rb_sprintf("cannot make a shareable Proc because " + "the outer variable '%" PRIsVALUE "' may be reassigned.", name); + rb_exc_raise(rb_exc_new_str(rb_eRactorIsolationError, msg)); + } + + // check shareable VALUE v = src_env->env[j]; if (!rb_ractor_shareable_p(v)) { VALUE name = rb_id2str(id); - VALUE msg = rb_sprintf("can not make shareable Proc because it can refer" + VALUE msg = rb_sprintf("cannot make a shareable Proc because it can refer" " unshareable object %+" PRIsVALUE " from ", v); if (name) rb_str_catf(msg, "variable '%" PRIsVALUE "'", name); @@ -1403,12 +1413,18 @@ proc_shared_outer_variables(struct rb_id_table *outer_variables, bool isolate, c } VALUE -rb_proc_isolate_bang(VALUE self) +rb_proc_isolate_bang(VALUE self, VALUE replace_self) { const rb_iseq_t *iseq = vm_proc_iseq(self); if (iseq) { rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(self); + + if (!UNDEF_P(replace_self)) { + VM_ASSERT(rb_ractor_shareable_p(replace_self)); + RB_OBJ_WRITE(self, &proc->block.as.captured.self, replace_self); + } + if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet"); if (ISEQ_BODY(iseq)->outer_variables) { @@ -1427,17 +1443,22 @@ VALUE rb_proc_isolate(VALUE self) { VALUE dst = rb_proc_dup(self); - rb_proc_isolate_bang(dst); + rb_proc_isolate_bang(dst, Qundef); return dst; } VALUE -rb_proc_ractor_make_shareable(VALUE self) +rb_proc_ractor_make_shareable(VALUE self, VALUE replace_self) { const rb_iseq_t *iseq = vm_proc_iseq(self); if (iseq) { rb_proc_t *proc = (rb_proc_t *)RTYPEDDATA_DATA(self); + + if (!UNDEF_P(replace_self)) { + RB_OBJ_WRITE(self, &proc->block.as.captured.self, replace_self); + } + if (proc->block.type != block_type_iseq) rb_raise(rb_eRuntimeError, "not supported yet"); if (!rb_ractor_shareable_p(vm_block_self(&proc->block))) { @@ -1458,6 +1479,8 @@ rb_proc_ractor_make_shareable(VALUE self) } rb_obj_freeze(self); + FL_SET_RAW(self, RUBY_FL_SHAREABLE); + return self; } @@ -496,6 +496,12 @@ struct rb_iseq_constant_body { const ID *local_table; /* must free */ + enum lvar_state { + lvar_uninitialized, + lvar_initialized, + lvar_reassigned, + } *lvar_states; + /* catch table */ struct iseq_catch_table *catch_table; @@ -1285,8 +1291,8 @@ typedef struct { RUBY_SYMBOL_EXPORT_BEGIN VALUE rb_proc_isolate(VALUE self); -VALUE rb_proc_isolate_bang(VALUE self); -VALUE rb_proc_ractor_make_shareable(VALUE self); +VALUE rb_proc_isolate_bang(VALUE self, VALUE replace_self); +VALUE rb_proc_ractor_make_shareable(VALUE proc, VALUE replace_self); RUBY_SYMBOL_EXPORT_END typedef struct { |
