Skip to content
69 changes: 37 additions & 32 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
5 changes: 4 additions & 1 deletion gc/default/default.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 2 additions & 6 deletions jit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion lib/ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
22 changes: 22 additions & 0 deletions ractor_sync.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down
1 change: 1 addition & 0 deletions symbol.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
41 changes: 41 additions & 0 deletions test/json/json_generator_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
18 changes: 18 additions & 0 deletions test/ruby/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions test/test_ipaddr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
Expand All @@ -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

Expand Down