From 56b67f1684bf1955cf69fc06701e2a710898bd9b Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 17 Dec 2025 12:17:30 -0500 Subject: [PATCH] ObjectSpace.{dump,dump_all,dump_shapes} needs vm barrier. (#15569) This allows these methods to be called from ractors. Add new exported function `rb_vm_lock_with_barrier()` that requires users to include "vm_sync.h" --- ext/objspace/objspace_dump.c | 56 +++++++++++++++++++++++++++++----- test/objspace/test_objspace.rb | 21 +++++++++++++ vm_sync.c | 11 +++++++ vm_sync.h | 4 +++ 4 files changed, 85 insertions(+), 7 deletions(-) diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index 30829d5ee1..da64698346 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -29,7 +29,7 @@ #include "ruby/util.h" #include "ruby/io.h" #include "vm_callinfo.h" -#include "vm_core.h" +#include "vm_sync.h" RUBY_EXTERN const char ruby_hexdigits[]; @@ -771,15 +771,16 @@ dump_result(struct dump_config *dc) return dc->given_output; } -/* :nodoc: */ static VALUE -objspace_dump(VALUE os, VALUE obj, VALUE output) +dump_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE obj = ((VALUE*)args_p)[0]; + VALUE output = ((VALUE*)args_p)[1]; + if (!RB_SPECIAL_CONST_P(obj)) { dc.cur_page_slot_size = rb_gc_obj_slot_size(obj); } - dump_output(&dc, output, Qnil, Qnil, Qnil); dump_object(obj, &dc); @@ -787,6 +788,16 @@ objspace_dump(VALUE os, VALUE obj, VALUE output) return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump(VALUE os, VALUE obj, VALUE output) +{ + VALUE args[2]; + args[0] = obj; + args[1] = output; + return rb_vm_lock_with_barrier(dump_locked, (void*)args); +} + static void shape_id_i(shape_id_t shape_id, void *data) { @@ -835,11 +846,15 @@ shape_id_i(shape_id_t shape_id, void *data) dump_append(dc, "}\n"); } -/* :nodoc: */ static VALUE -objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) +dump_all_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE output = ((VALUE*)args_p)[0]; + VALUE full = ((VALUE*)args_p)[1]; + VALUE since = ((VALUE*)args_p)[2]; + VALUE shapes = ((VALUE*)args_p)[3]; + dump_output(&dc, output, full, since, shapes); if (!dc.partial_dump || dc.since == 0) { @@ -860,9 +875,23 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) /* :nodoc: */ static VALUE -objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) +{ + VALUE args[4]; + args[0] = output; + args[1] = full; + args[2] = since; + args[3] = shapes; + return rb_vm_lock_with_barrier(dump_all_locked, (void*)args); +} + +static VALUE +dump_shapes_locked(void *args_p) { struct dump_config dc = {0,}; + VALUE output = ((VALUE*)args_p)[0]; + VALUE shapes = ((VALUE*)args_p)[1]; + dump_output(&dc, output, Qfalse, Qnil, shapes); if (RTEST(shapes)) { @@ -871,6 +900,16 @@ objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +{ + VALUE args[2]; + args[0] = output; + args[1] = shapes; + return rb_vm_lock_with_barrier(dump_shapes_locked, (void*)args); +} + void Init_objspace_dump(VALUE rb_mObjSpace) { @@ -878,6 +917,9 @@ Init_objspace_dump(VALUE rb_mObjSpace) #if 0 rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */ #endif +#ifdef HAVE_RB_EXT_RACTOR_SAFE + RB_EXT_RACTOR_SAFE(true); +#endif rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2); rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 4); diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index ea3c6aa719..3d5b2a4d2a 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -827,6 +827,27 @@ class TestObjSpace < Test::Unit::TestCase end end + def test_dump_all_with_ractors + assert_ractor("#{<<-"begin;"}#{<<-'end;'}") + begin; + require "objspace" + require "tempfile" + require "json" + rs = 4.times.map do + Ractor.new do + Tempfile.create do |f| + ObjectSpace.dump_all(output: f) + f.close + File.readlines(f.path).each do |line| + JSON.parse(line) + end + end + end + end + rs.each(&:join) + end; + end + def test_dump_uninitialized_file assert_in_out_err(%[-robjspace], <<-RUBY) do |(output), (error)| puts ObjectSpace.dump(File.allocate) diff --git a/vm_sync.c b/vm_sync.c index 6d93720701..aca83dde5a 100644 --- a/vm_sync.c +++ b/vm_sync.c @@ -297,3 +297,14 @@ rb_ec_vm_lock_rec_release(const rb_execution_context_t *ec, VM_ASSERT(recorded_lock_rec == rb_ec_vm_lock_rec(ec)); } + +VALUE +rb_vm_lock_with_barrier(VALUE (*func)(void *args), void *args) +{ + VALUE result = 0; + RB_VM_LOCKING() { + rb_vm_barrier(); + result = func(args); + } + return result; +} diff --git a/vm_sync.h b/vm_sync.h index 15dfff4d0b..314a2238a9 100644 --- a/vm_sync.h +++ b/vm_sync.h @@ -28,6 +28,10 @@ void rb_vm_lock_leave_body_nb(unsigned int *lev APPEND_LOCATION_ARGS); void rb_vm_lock_leave_body(unsigned int *lev APPEND_LOCATION_ARGS); void rb_vm_barrier(void); +RUBY_SYMBOL_EXPORT_BEGIN +VALUE rb_vm_lock_with_barrier(VALUE (*func)(void *args), void *args); +RUBY_SYMBOL_EXPORT_END + #if RUBY_DEBUG // GET_VM() #include "vm_core.h"