Skip to content
Merged
60 changes: 55 additions & 5 deletions bootstraptest/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions internal/numeric.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
5 changes: 0 additions & 5 deletions io_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
6 changes: 4 additions & 2 deletions lib/rubygems/commands/setup_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion numeric.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
22 changes: 13 additions & 9 deletions ractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions test/objspace/test_objspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
28 changes: 28 additions & 0 deletions test/objspace/test_ractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 0 additions & 3 deletions test/ruby/test_io_buffer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions test/rubygems/test_gem_commands_setup_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -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; i<VM_GLOBAL_CC_CACHE_TABLE_SIZE; i++) {
Expand Down
27 changes: 25 additions & 2 deletions vm_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,9 @@ typedef struct rb_vm_struct {
VALUE cmd[RUBY_NSIG];
} trap_list;

/* hook (for internal events: NEWOBJ, FREEOBJ, GC events, etc.) */
rb_hook_list_t global_hooks;

/* postponed_job (async-signal-safe, and thread-safe) */
struct rb_postponed_job_queue *postponed_job_queue;

Expand Down Expand Up @@ -2311,11 +2314,31 @@ rb_ec_ractor_hooks(const rb_execution_context_t *ec)
return &cr_pub->hooks;
}

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)
Expand Down
Loading