diff --git a/.github/labeler.yml b/.github/labeler.yml index 9cc3a60e349dc6..35214948e5880f 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -7,40 +7,45 @@ Backport: - base-branch: 'ruby_4_\d' jit: -- changed-files: - - any-glob-to-any-file: - # JIT sources (Rust + C + Ruby) - - 'jit/**' - # All of {y,z}jit/ except the generated cruby_bindings.inc.rs files. - # `!(...)` can't span a `/`, so each pair splits the tree into - # non-Rust files and Rust files not named cruby_bindings.inc.rs; - # their union is everything but the bindings file. - - 'yjit/**/!(*.rs)' - - 'yjit/**/!(cruby_bindings.inc).rs' - - 'zjit/**/!(*.rs)' - - 'zjit/**/!(cruby_bindings.inc).rs' - - 'jit.c' - - 'jit_hook.rb' - - 'jit_undef.rb' - - '{y,z}jit.{c,h,rb}' +- all: + - base-branch: '^master$' + # Skip dependabot's PRs, which bump actions in the CI files. + # They're noisy in notifications, and they're auto-merged anyway. + - head-branch: '^(?!dependabot/)' + - changed-files: + - any-glob-to-any-file: + # JIT sources (Rust + C + Ruby) + - 'jit/**' + # All of {y,z}jit/ except the generated cruby_bindings.inc.rs files. + # `!(...)` can't span a `/`, so each pair splits the tree into + # non-Rust files and Rust files not named cruby_bindings.inc.rs; + # their union is everything but the bindings file. + - 'yjit/**/!(*.rs)' + - 'yjit/**/!(cruby_bindings.inc).rs' + - 'zjit/**/!(*.rs)' + - 'zjit/**/!(cruby_bindings.inc).rs' + - 'jit.c' + - 'jit_hook.rb' + - 'jit_undef.rb' + - '{y,z}jit.{c,h,rb}' - # Build + GC fast paths - - 'defs/jit.mk' - - 'gc/**/zjit_fastpath.h' + # Build + GC fast paths + - 'defs/jit.mk' + - 'gc/**/zjit_fastpath.h' - # Docs - - 'doc/jit/**' + # Docs + - 'doc/jit/**' - # Specs + tests - - 'spec/zjit.mspec' - - 'test/.excludes-zjit/**' - - 'test/lib/jit_support.rb' - - 'test/ruby/test_*jit*.rb' - - 'bootstraptest/test_*jit*.rb' + # Specs + tests + - 'spec/zjit.mspec' + - 'test/.excludes-zjit/**' + - 'test/lib/jit_support.rb' + - 'test/ruby/test_*jit*.rb' + - 'bootstraptest/test_*jit*.rb' - # Tooling - - 'tool/zjit_*' - - 'tool/ruby_vm/**/*zjit*' + # Tooling + - 'tool/zjit_*' + - 'tool/ruby_vm/**/*zjit*' - # CI - - '.github/workflows/*jit*.yml' + # CI + - '.github/workflows/*jit*.yml' diff --git a/gc/default/default.c b/gc/default/default.c index 139fb142c0b24b..df1e914f14ea07 100644 --- a/gc/default/default.c +++ b/gc/default/default.c @@ -12609,7 +12609,6 @@ rb_gc_impl_objspace_init(void *objspace_ptr) gc_config_full_mark_set(TRUE); - objspace->flags.measure_gc = true; malloc_limit = gc_params.malloc_limit_min; objspace->shareable_objects_limit = SHAREABLE_OBJECTS_LIMIT_MIN; #ifdef MALLOC_COUNTERS_NEED_LOCK @@ -12648,6 +12647,10 @@ rb_gc_impl_objspace_init(void *objspace_ptr) #endif gc_params.heap_init_bytes = GC_HEAP_INIT_BYTES; } + // GC.measure_total_time= sets the caller's objspace only; a new Ractor's follows + // its creator's, which is the objspace running this init (main starts it on). + objspace->flags.measure_gc = global_objspace->main_objspace == objspace ? true + : ((rb_objspace_t *)rb_gc_get_objspace())->flags.measure_gc; rb_darray_make_without_gc(&objspace->heap_pages.sorted, 0); rb_darray_make_without_gc(&objspace->weak_references, 0); diff --git a/jit.c b/jit.c index 7f6f161f2f4cdf..24b7e1956a4171 100644 --- a/jit.c +++ b/jit.c @@ -796,12 +796,8 @@ rb_jit_reserve_addr_space(uint32_t mem_size) // Check that the memory mapping was successful if (mem_block == MAP_FAILED) { - perror("ruby: jit: mmap:"); - if(errno == ENOMEM) { - // No crash report if it's only insufficient memory - exit(EXIT_FAILURE); - } - rb_bug("mmap failed"); + perror("ruby: jit: Fatal mmap failure:"); + abort(); } return mem_block; diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a71772e32b05ad..e0c000621a2255 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -378,7 +378,7 @@ def link_local_multicast? when Socket::AF_INET6 @addr & 0xffff_0000_0000_0000_0000_0000_0000_0000 == 0xff02_0000_0000_0000_0000_0000_0000_0000 || # ff02::/16 (@addr >> 32 == 0xffff && ( - @addr & 0xffff0000 == 0xe0000000 # ::ffff:224.0.0.0/24 + @addr & 0xffffff00 == 0xe0000000 # ::ffff:224.0.0.0/24 )) else raise AddressFamilyError, "unsupported address family" diff --git a/ractor_sync.c b/ractor_sync.c index 3cc61f52d19706..eda4754236a7f4 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -687,11 +687,33 @@ ractor_reap_dead_ports_i(st_data_t port_id, st_data_t val, st_data_t dat) } } +/* A message is only moved from recv_queue to its port queue when the owner receives or + * closes, so one addressed to a port that died first is left here, out of the sweep + * above. Its port is gone from the table by now: drop it. */ +static void +ractor_reap_undeliverable_messages(rb_ractor_t *r) +{ + struct ractor_queue *recv_q = r->sync.recv_queue; + if (recv_q == NULL) return; + + struct ractor_basket *b, *nxt; + ccan_list_for_each_safe(&recv_q->set, b, nxt, node) { + if (!st_lookup(r->sync.ports, b->port_id, NULL)) { + ccan_list_del_init(&b->node); + ractor_basket_free(b); + } + } +} + void rb_ractor_reap_dead_ports(rb_ractor_t *r) { + /* No sync lock here: the caller's gate is what keeps foreign senders out. */ + VM_ASSERT(rb_gc_single_objspace_p() || rb_gc_during_global_gc_p()); + if (r->sync.ports) { st_foreach(r->sync.ports, ractor_reap_dead_ports_i, 0); + ractor_reap_undeliverable_messages(r); } } diff --git a/symbol.c b/symbol.c index a6b5741b45e004..678676b3d67c03 100644 --- a/symbol.c +++ b/symbol.c @@ -1217,6 +1217,7 @@ rb_sym_all_symbols(void) VALUE ary; GLOBAL_SYMBOLS_LOCKING(symbols) { + rb_vm_barrier(); ary = rb_ary_new2(rb_concurrent_set_size(symbols->sym_set)); rb_concurrent_set_foreach_with_replace(symbols->sym_set, symbols_i, (void *)ary); } diff --git a/test/json/json_generator_test.rb b/test/json/json_generator_test.rb index fcfc968f21a918..1f4b5d9495256e 100755 --- a/test/json/json_generator_test.rb +++ b/test/json/json_generator_test.rb @@ -516,6 +516,47 @@ def test_configure_using_configure_and_merge assert_equal '5', state2.array_nl end + def test_configure_only_writes_the_string_options_it_is_given + state = JSON.state.new(indent: '1', space: '2', space_before: '3', object_nl: '4', array_nl: '5') + state.configure(space: '9') + assert_equal '1', state.indent + assert_equal '9', state.space + assert_equal '3', state.space_before + assert_equal '4', state.object_nl + assert_equal '5', state.array_nl + state.merge(array_nl: '8') + assert_equal '1', state.indent + assert_equal '9', state.space + assert_equal '3', state.space_before + assert_equal '4', state.object_nl + assert_equal '8', state.array_nl + end + + def test_configure_keeps_the_layout_of_a_pretty_state + state = JSON.state.new(indent: ' ', object_nl: "\n", array_nl: "\n") + state.configure(depth: 0) + assert_equal %({\n "foo":[\n 1\n ]\n}), state.generate({ 'foo' => [1] }) + end + + def test_configure_only_writes_the_other_options_it_is_given + omit 'JRuby resets the non-string options' if RUBY_ENGINE == 'jruby' + state = JSON.state.new(max_nesting: 3, allow_nan: true, ascii_only: true, script_safe: true) + state.configure(indent: '1') + assert_equal '1', state.indent + assert_equal 3, state.max_nesting + assert_equal true, state.allow_nan? + assert_equal true, state.ascii_only? + assert_equal true, state.script_safe? + end + + def test_configure_writes_a_string_option_given_as_nil + omit 'JRuby keeps the previous value for an explicit nil' if RUBY_ENGINE == 'jruby' + state = JSON.state.new(indent: '1', space: '2') + state.configure(indent: nil) + assert_equal '', state.indent + assert_equal '2', state.space + end + def test_configure_hash_conversion state = JSON.state.new state.configure(indent: '1') diff --git a/test/ruby/test_ractor.rb b/test/ruby/test_ractor.rb index 5b21ee361dfad2..59299229feb679 100644 --- a/test/ruby/test_ractor.rb +++ b/test/ruby/test_ractor.rb @@ -984,4 +984,22 @@ def test_attached_object_of_unshareable_object assert_equal true, Ractor.new { own = Object.new; own.singleton_class.attached_object.equal?(own) }.value RUBY end + + def test_port_undelivered_message_does_not_leak + omit 'not fixed for mmtk: it never calls rb_ractor_finish_marking, where the reap runs' unless GC.config[:implementation] == 'default' + # A message is only moved out of the receiving Ractor's incoming queue when it + # receives or closes. One addressed to a port that became unreachable first used to + # stay there for the life of the process, off-heap and invisible to ObjectSpace. + assert_no_memory_leak([], <<~'PREP', <<~'CODE', '[Bug #22122]', rss: true) + def t + port = Ractor::Port.new + 5.times { port << ("z" * (4 << 20)) } + end + # A few large payloads rather than many small ones, and a baseline taken at the + # high-water mark: small-allocation RSS creep alone reached 2.5x on macOS. + 5.times { t; GC.start } + PREP + 30.times { t; GC.start } + CODE + end end diff --git a/test/test_ipaddr.rb b/test/test_ipaddr.rb index ab645325a30809..7ef2136807a9f0 100644 --- a/test/test_ipaddr.rb +++ b/test/test_ipaddr.rb @@ -651,6 +651,10 @@ def test_multicast? def test_link_local_multicast? assert_equal(true, IPAddr.new('224.0.0.0').link_local_multicast?) assert_equal(true, IPAddr.new('224.0.0.0/24').link_local_multicast?) + assert_equal(true, IPAddr.new('224.0.0.255').link_local_multicast?) + + assert_equal(false, IPAddr.new('224.0.1.1').link_local_multicast?) + assert_equal(false, IPAddr.new('224.1.0.0').link_local_multicast?) assert_equal(false, IPAddr.new('225.0.0.0').link_local_multicast?) assert_equal(false, IPAddr.new('225.0.0.0/24').link_local_multicast?) @@ -668,6 +672,9 @@ def test_link_local_multicast? assert_equal(false, IPAddr.new('::').link_local_multicast?) assert_equal(true, IPAddr.new('::ffff:224.0.0.0').link_local_multicast?) + assert_equal(true, IPAddr.new('::ffff:224.0.0.255').link_local_multicast?) + assert_equal(false, IPAddr.new('::ffff:224.0.1.1').link_local_multicast?) + assert_equal(false, IPAddr.new('::ffff:224.1.0.0').link_local_multicast?) assert_equal(false, IPAddr.new('::ffff:225.0.0.0').link_local_multicast?) end