diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 9042f945f7f4f4..81dc2d6b8d04be 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -150,14 +150,14 @@ Ractor.shareable_lambda{ a }.call } -# Ractor.make_shareable issue for locals in proc [Bug #18023] +# Ractor.shareable_proc 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 = Proc.new { [v1, v2, v3, v4, v5] } Ractor.shareable_proc(&closure).call } -# Ractor.make_shareable makes a copy of given Proc +# Ractor.shareable_proc makes a copy of given Proc assert_equal '[true, true]', %q{ pr1 = Proc.new do self @@ -167,6 +167,56 @@ [pr1.call == self, pr2.call == nil] } +# Ractor.shareable_proc keeps the original Proc intact +assert_equal '[SyntaxError, [Object, 43, 43], Binding]', %q{ + a = 42 + pr1 = Proc.new do + [self.class, eval("a"), binding.local_variable_get(:a)] + end + a += 1 + pr2 = Ractor.shareable_proc(&pr1) + + r = [] + begin + pr2.call + rescue SyntaxError + r << SyntaxError + end + + r << pr1.call << pr1.binding.class +} + +# Ractor.make_shareable mutates the original Proc +# This is the current behavior, it's currently considered safe enough +# because in most cases it would raise anyway due to not-shared self or not-shared captured variable value +assert_equal '[[42, 42], Binding, true, SyntaxError, "Can\'t create Binding from isolated Proc"]', %q{ + a = 42 + pr1 = nil.instance_exec do + Proc.new do + [eval("a"), binding.local_variable_get(:a)] + end + end + + r = [pr1.call, pr1.binding.class] + + pr2 = Ractor.make_shareable(pr1) + r << pr1.equal?(pr2) + + begin + pr1.call + rescue SyntaxError + r << SyntaxError + end + + begin + r << pr1.binding + rescue ArgumentError + r << $!.message + end + + r +} + # Ractor::IsolationError cases assert_equal '3', %q{ ok = 0 @@ -181,9 +231,9 @@ begin cond = false - a = 1 - a = 2 if cond - Ractor.shareable_proc{a} + b = 1 + b = 2 if cond + Ractor.shareable_proc{b} rescue Ractor::IsolationError => e ok += 1 end diff --git a/internal/numeric.h b/internal/numeric.h index 75181a7f168620..d3905f048c2ab5 100644 --- a/internal/numeric.h +++ b/internal/numeric.h @@ -164,6 +164,11 @@ union rb_int128 { }; typedef union rb_int128 rb_int128_t; +union uint128_int128_conversion { + rb_uint128_t uint128; + rb_int128_t int128; +}; + // Conversion functions for 128-bit integers: rb_uint128_t rb_numeric_to_uint128(VALUE x); rb_int128_t rb_numeric_to_int128(VALUE x); diff --git a/io_buffer.c b/io_buffer.c index b81527ff71147a..55f1933194ef7b 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -1928,11 +1928,6 @@ ruby_swap128_uint(rb_uint128_t x) return result; } -union uint128_int128_conversion { - rb_uint128_t uint128; - rb_int128_t int128; -}; - static inline rb_int128_t ruby_swap128_int(rb_int128_t x) { diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index 8fcece5d5ce252..175599967cf62d 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -402,8 +402,10 @@ def install_default_bundler_gem(bin_dir) install_dir: default_dir, wrappers: true ) - installer.install - File.delete installer.spec_file + # We need to install only executable and default spec files. + # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + installer.extract_bin + installer.generate_bin installer.write_default_spec ensure FileUtils.rm_f built_gem diff --git a/numeric.c b/numeric.c index d9e837644ffa05..1942a6164f616a 100644 --- a/numeric.c +++ b/numeric.c @@ -3638,7 +3638,10 @@ rb_int128_to_numeric(rb_int128_t n) } else { // Positive value - return rb_uint128_to_numeric(*(rb_uint128_t*)&n); + union uint128_int128_conversion conversion = { + .int128 = n + }; + return rb_uint128_to_numeric(conversion.uint128); } #endif } diff --git a/ractor.c b/ractor.c index 9fec70a1dfab71..f65a4f79c1f8e8 100644 --- a/ractor.c +++ b/ractor.c @@ -72,15 +72,17 @@ ractor_lock(rb_ractor_t *r, const char *file, int line) ASSERT_ractor_unlocking(r); rb_native_mutex_lock(&r->sync.lock); - if (rb_current_execution_context(false)) { - VM_ASSERT(!GET_RACTOR()->malloc_gc_disabled); - GET_RACTOR()->malloc_gc_disabled = true; + const rb_execution_context_t *ec = rb_current_ec_noinline(); + if (ec) { + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + VM_ASSERT(!cr->malloc_gc_disabled); + cr->malloc_gc_disabled = true; } #if RACTOR_CHECK_MODE > 0 - if (rb_current_execution_context(false) != NULL) { - rb_ractor_t *cr = rb_current_ractor_raw(false); - r->sync.locked_by = cr ? rb_ractor_self(cr) : Qundef; + if (ec != NULL) { + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + r->sync.locked_by = rb_ractor_self(cr); } #endif @@ -105,9 +107,11 @@ ractor_unlock(rb_ractor_t *r, const char *file, int line) r->sync.locked_by = Qnil; #endif - if (rb_current_execution_context(false)) { - VM_ASSERT(GET_RACTOR()->malloc_gc_disabled); - GET_RACTOR()->malloc_gc_disabled = false; + const rb_execution_context_t *ec = rb_current_ec_noinline(); + if (ec) { + rb_ractor_t *cr = rb_ec_ractor_ptr(ec); + VM_ASSERT(cr->malloc_gc_disabled); + cr->malloc_gc_disabled = false; } rb_native_mutex_unlock(&r->sync.lock); diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index e35fc0c14ecfe5..ea3c6aa7192146 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -288,6 +288,33 @@ def test_trace_object_allocations_gc_stress assert true # success end + def test_trace_object_allocations_with_other_tracepoint + # Test that ObjectSpace.trace_object_allocations isn't changed by changes + # to another tracepoint + line_tp = TracePoint.new(:line) { } + + ObjectSpace.trace_object_allocations_start + + obj1 = Object.new; line1 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1) + assert_equal line1, ObjectSpace.allocation_sourceline(obj1) + + line_tp.enable + + obj2 = Object.new; line2 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2) + assert_equal line2, ObjectSpace.allocation_sourceline(obj2) + + line_tp.disable + + obj3 = Object.new; line3 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3) + assert_equal line3, ObjectSpace.allocation_sourceline(obj3) + ensure + ObjectSpace.trace_object_allocations_stop + ObjectSpace.trace_object_allocations_clear + end + def test_trace_object_allocations_compaction omit "compaction is not supported on this platform" unless GC.respond_to?(:compact) diff --git a/test/objspace/test_ractor.rb b/test/objspace/test_ractor.rb index eb3044cda3c3c3..996d83fbd214dd 100644 --- a/test/objspace/test_ractor.rb +++ b/test/objspace/test_ractor.rb @@ -52,4 +52,32 @@ def fin ractors.each(&:join) RUBY end + + def test_trace_object_allocations_with_ractor_tracepoint + # Test that ObjectSpace.trace_object_allocations works globally across all Ractors + assert_ractor(<<~'RUBY', require: 'objspace') + ObjectSpace.trace_object_allocations do + obj1 = Object.new; line1 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj1) + assert_equal line1, ObjectSpace.allocation_sourceline(obj1) + + r = Ractor.new { + obj = Object.new; line = __LINE__ + [line, obj] + } + + obj2 = Object.new; line2 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj2) + assert_equal line2, ObjectSpace.allocation_sourceline(obj2) + + expected_line, ractor_obj = r.value + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(ractor_obj) + assert_equal expected_line, ObjectSpace.allocation_sourceline(ractor_obj) + + obj3 = Object.new; line3 = __LINE__ + assert_equal __FILE__, ObjectSpace.allocation_sourcefile(obj3) + assert_equal line3, ObjectSpace.allocation_sourceline(obj3) + end + RUBY + end end diff --git a/test/ruby/test_io_buffer.rb b/test/ruby/test_io_buffer.rb index 9ff22b4bb356eb..cc7998842313e8 100644 --- a/test/ruby/test_io_buffer.rb +++ b/test/ruby/test_io_buffer.rb @@ -852,9 +852,6 @@ def test_integer_endianness_swapping assert_equal value, result_be, "#{be_type}: round-trip failed" # Verify byte patterns are different when endianness differs from host - le_bytes = buffer.get_string(0, buffer_size) - be_bytes = buffer.get_string(buffer_size, buffer_size) - if host_is_le # On little-endian host: le_type should match host, be_type should be swapped # So the byte patterns should be different (unless value is symmetric) diff --git a/test/rubygems/test_gem_commands_setup_command.rb b/test/rubygems/test_gem_commands_setup_command.rb index dfd951268dc962..b33e05ab28dc55 100644 --- a/test/rubygems/test_gem_commands_setup_command.rb +++ b/test/rubygems/test_gem_commands_setup_command.rb @@ -31,6 +31,7 @@ def setup gemspec = util_spec "bundler", "9.9.9" do |s| s.bindir = "exe" s.executables = ["bundle", "bundler"] + s.files = ["lib/bundler.rb"] end File.open "bundler/bundler.gemspec", "w" do |io| @@ -222,6 +223,9 @@ def test_install_default_bundler_gem assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}" assert_path_exist "#{Gem.dir}/gems/bundler-audit-1.0.0" + + assert_path_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/exe/bundle" + assert_path_not_exist "#{Gem.dir}/gems/bundler-#{bundler_version}/lib/bundler.rb" end def test_install_default_bundler_gem_with_default_gems_not_installed_at_default_dir diff --git a/vm.c b/vm.c index b15ee0ba236b57..f832fe401f124b 100644 --- a/vm.c +++ b/vm.c @@ -3314,6 +3314,8 @@ rb_vm_mark(void *ptr) rb_gc_mark_values(RUBY_NSIG, vm->trap_list.cmd); + rb_hook_list_mark(&vm->global_hooks); + rb_id_table_foreach_values(vm->negative_cme_table, vm_mark_negative_cme, NULL); rb_mark_tbl_no_pin(vm->overloaded_cme_table); for (i=0; ihooks; } +static inline rb_hook_list_t * +rb_vm_global_hooks(const rb_execution_context_t *ec) +{ + return &rb_ec_vm_ptr(ec)->global_hooks; +} + +static inline rb_hook_list_t * +rb_ec_hooks(const rb_execution_context_t *ec, rb_event_flag_t event) +{ + // Should be a single bit set + VM_ASSERT(event != 0 && ((event - 1) & event) == 0); + + if (event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK) { + return rb_vm_global_hooks(ec); + } + else { + return rb_ec_ractor_hooks(ec); + } +} + #define EXEC_EVENT_HOOK(ec_, flag_, self_, id_, called_id_, klass_, data_) \ - EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_ractor_hooks(ec_), flag_, self_, id_, called_id_, klass_, data_, 0) + EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_hooks(ec_, flag_), flag_, self_, id_, called_id_, klass_, data_, 0) #define EXEC_EVENT_HOOK_AND_POP_FRAME(ec_, flag_, self_, id_, called_id_, klass_, data_) \ - EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_ractor_hooks(ec_), flag_, self_, id_, called_id_, klass_, data_, 1) + EXEC_EVENT_HOOK_ORIG(ec_, rb_ec_hooks(ec_, flag_), flag_, self_, id_, called_id_, klass_, data_, 1) static inline void rb_exec_event_hook_script_compiled(rb_execution_context_t *ec, const rb_iseq_t *iseq, VALUE eval_script) diff --git a/vm_trace.c b/vm_trace.c index 58424008fd4f0e..b2fc436a96b210 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -127,9 +127,15 @@ update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events rb_clear_bf_ccs(); } - ruby_vm_event_flags = new_events; - ruby_vm_event_enabled_global_flags |= new_events; - rb_objspace_set_event_hook(new_events); + // FIXME: Which flags are enabled globally comes from multiple lists, one + // per-ractor and a global list. + // This incorrectly assumes the lists have mutually exclusive flags set. + // This is true for the global (objspace) events, but not for ex. multiple + // Ractors listening for the same iseq events. + rb_event_flag_t new_events_global = (ruby_vm_event_flags & ~prev_events) | new_events; + ruby_vm_event_flags = new_events_global; + ruby_vm_event_enabled_global_flags |= new_events_global; + rb_objspace_set_event_hook(new_events_global); // Invalidate JIT code as needed if (first_time_iseq_events_p || enable_c_call || enable_c_return) { @@ -188,7 +194,17 @@ hook_list_connect(VALUE list_owner, rb_hook_list_t *list, rb_event_hook_t *hook, static void connect_event_hook(const rb_execution_context_t *ec, rb_event_hook_t *hook) { - rb_hook_list_t *list = rb_ec_ractor_hooks(ec); + rb_hook_list_t *list; + + /* internal events are VM-global, non-internal are ractor-local */ + VM_ASSERT(!(hook->events & RUBY_INTERNAL_EVENT_OBJSPACE_MASK) || !(hook->events & ~RUBY_INTERNAL_EVENT_OBJSPACE_MASK)); + + if (hook->events & RUBY_INTERNAL_EVENT_OBJSPACE_MASK) { + list = rb_vm_global_hooks(ec); + } + else { + list = rb_ec_ractor_hooks(ec); + } hook_list_connect(Qundef, list, hook, TRUE); } @@ -272,11 +288,9 @@ clean_hooks_check(rb_hook_list_t *list) #define MATCH_ANY_FILTER_TH ((rb_thread_t *)1) -/* if func is 0, then clear all funcs */ static int -remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) +remove_event_hook_from_list(rb_hook_list_t *list, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) { - rb_hook_list_t *list = rb_ec_ractor_hooks(ec); int ret = 0; rb_event_hook_t *hook = list->hooks; @@ -297,6 +311,18 @@ remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th return ret; } +/* if func is 0, then clear all funcs */ +static int +remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) +{ + int ret = 0; + + ret += remove_event_hook_from_list(rb_ec_ractor_hooks(ec), filter_th, func, data); + ret += remove_event_hook_from_list(rb_vm_global_hooks(ec), filter_th, func, data); + + return ret; +} + static int rb_threadptr_remove_event_hook(const rb_execution_context_t *ec, const rb_thread_t *filter_th, rb_event_hook_func_t func, VALUE data) { @@ -421,8 +447,10 @@ rb_exec_event_hooks(rb_trace_arg_t *trace_arg, rb_hook_list_t *hooks, int pop_p) { rb_execution_context_t *ec = trace_arg->ec; - if (UNLIKELY(trace_arg->event & RUBY_INTERNAL_EVENT_MASK)) { - if (ec->trace_arg && (ec->trace_arg->event & RUBY_INTERNAL_EVENT_MASK)) { + if (UNLIKELY(trace_arg->event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK)) { + VM_ASSERT(hooks == rb_vm_global_hooks(ec)); + + if (ec->trace_arg && (ec->trace_arg->event & RUBY_INTERNAL_EVENT_OBJSPACE_MASK)) { /* skip hooks because this thread doing INTERNAL_EVENT */ } else { @@ -430,7 +458,7 @@ rb_exec_event_hooks(rb_trace_arg_t *trace_arg, rb_hook_list_t *hooks, int pop_p) ec->trace_arg = trace_arg; /* only global hooks */ - exec_hooks_unprotected(ec, rb_ec_ractor_hooks(ec), trace_arg); + exec_hooks_unprotected(ec, hooks, trace_arg); ec->trace_arg = prev_trace_arg; } }