diff --git a/concurrent_set.c b/concurrent_set.c index 42ff6ef43f799e..cdc7879f4cd307 100644 --- a/concurrent_set.c +++ b/concurrent_set.c @@ -2,6 +2,7 @@ #include "internal/gc.h" #include "internal/concurrent_set.h" #include "ruby/atomic.h" +#include "ruby/ractor.h" #include "vm_sync.h" #define CONCURRENT_SET_CONTINUATION_BIT ((VALUE)1 << (sizeof(VALUE) * CHAR_BIT - 1)) @@ -102,6 +103,9 @@ rb_concurrent_set_new(const struct rb_concurrent_set_funcs *funcs, int capacity) set->funcs = funcs; set->entries = ZALLOC_N(struct concurrent_set_entry, capacity); set->capacity = capacity; + /* The set is reachable from every Ractor (e.g. via C globals such as the + * frozen-string and symbol tables), so mark it shareable. */ + RB_OBJ_SET_SHAREABLE(obj); return obj; } diff --git a/depend b/depend index 22ae43b21ad589..8b764bd060c49c 100644 --- a/depend +++ b/depend @@ -2353,6 +2353,7 @@ concurrent_set.$(OBJEXT): {$(VPATH)}missing.h concurrent_set.$(OBJEXT): {$(VPATH)}node.h concurrent_set.$(OBJEXT): {$(VPATH)}onigmo.h concurrent_set.$(OBJEXT): {$(VPATH)}oniguruma.h +concurrent_set.$(OBJEXT): {$(VPATH)}ractor.h concurrent_set.$(OBJEXT): {$(VPATH)}ruby_assert.h concurrent_set.$(OBJEXT): {$(VPATH)}ruby_atomic.h concurrent_set.$(OBJEXT): {$(VPATH)}rubyparser.h diff --git a/id_table.c b/id_table.c index f45cd2c61e1199..a1cc9043b6cff2 100644 --- a/id_table.c +++ b/id_table.c @@ -390,6 +390,10 @@ rb_managed_id_table_dup(VALUE old_table) { struct rb_id_table *new_tbl; VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, RTYPEDDATA_TYPE(old_table), new_tbl); + /* A managed id table hangs off VM-global state (e.g. a shape tree's edge + * table grows via this dup) and is reachable from every Ractor, so mark it + * shareable. */ + RB_OBJ_SET_SHAREABLE(obj); struct rb_id_table *old_tbl = managed_id_table_ptr(old_table); rb_id_table_init(new_tbl, old_tbl->num + 1); rb_id_table_foreach(old_tbl, managed_id_table_dup_i, new_tbl); diff --git a/symbol.c b/symbol.c index b157d2677cdae3..513e7ce33774c0 100644 --- a/symbol.c +++ b/symbol.c @@ -278,6 +278,9 @@ set_id_entry(rb_symbols_t *symbols, rb_id_serial_t num, VALUE str, VALUE sym) if (idx >= (size_t)RARRAY_LEN(ids) || NIL_P(id_entry_list = rb_ary_entry(ids, (long)idx))) { rb_darray_make(&entries, ID_ENTRY_UNIT); id_entry_list = TypedData_Wrap_Struct(0, &sym_id_entry_list_type, entries); + /* Reachable from every Ractor via the global symbol table, so mark it + * shareable. */ + RB_OBJ_SET_SHAREABLE(id_entry_list); rb_ary_store(ids, (long)idx, id_entry_list); } else { diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index e7eb0cd4b34fe7..65c4756dc0bc02 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -372,6 +372,13 @@ def test_ractor_new_raises_isolation_error_if_proc_uses_yield end end + def test_ractor_does_not_inherit_fiber_storage + assert_ractor(<<~'RUBY') + Fiber[:key] = "creator" + assert_nil Ractor.new { Fiber[:key] }.value + RUBY + end + def assert_make_shareable(obj) refute Ractor.shareable?(obj), "object was already shareable" Ractor.make_shareable(obj) diff --git a/vm_trace.c b/vm_trace.c index 8ef805dc1fe9d4..542b1869e08a84 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -1950,8 +1950,10 @@ rb_postponed_job_flush(rb_vm_t *vm) while (triggered_bits) { unsigned int i = bit_length(triggered_bits) - 1; triggered_bits ^= ((1UL) << i); /* toggle ith bit off */ - rb_postponed_job_func_t func = pjq->table[i].func; - void *data = pjq->table[i].data; + /* Read atomically to pair with the atomic CAS/EXCHANGE stores in + * rb_postponed_job_preregister, which can run on another thread. */ + rb_postponed_job_func_t func = (rb_postponed_job_func_t)(uintptr_t)RUBY_ATOMIC_PTR_LOAD(pjq->table[i].func); + void *data = RUBY_ATOMIC_PTR_LOAD(pjq->table[i].data); (func)(data); }