Ractor: revert to moving object bytes, but size pool aware

Using `rb_obj_clone` introduce other problems, such as `initialize_*`
callbacks invocation in the context of the parent ractor.

So we can revert back to copy the content of the object slots,
but in a way that is aware of size pools.
This commit is contained in:
Jean Boussier 2025-04-04 13:28:51 +02:00
parent eb765913c1
commit 085cc6e434
Notes: git 2025-04-04 14:26:46 +00:00
5 changed files with 73 additions and 11 deletions

View File

@ -2101,3 +2101,24 @@ assert_equal 'ok', %q{
:fail
end
}
# move objects inside frozen containers
assert_equal 'ok', %q{
ractor = Ractor.new { Ractor.receive }
obj = Array.new(10, 42)
original = obj.dup
ractor.send([obj].freeze, move: true)
roundtripped_obj = ractor.take[0]
roundtripped_obj == original ? :ok : roundtripped_obj
}
# move object with generic ivar
assert_equal 'ok', %q{
ractor = Ractor.new { Ractor.receive }
obj = Array.new(10, 42)
obj.instance_variable_set(:@array, [1])
ractor.send(obj, move: true)
roundtripped_obj = ractor.take
roundtripped_obj.instance_variable_get(:@array) == [1] ? :ok : roundtripped_obj
}

8
gc.c
View File

@ -2658,14 +2658,6 @@ rb_gc_mark_roots(void *objspace, const char **categoryp)
#define TYPED_DATA_REFS_OFFSET_LIST(d) (size_t *)(uintptr_t)RTYPEDDATA(d)->type->function.dmark
void
rb_gc_ractor_moved(VALUE dest, VALUE src)
{
rb_gc_obj_free(rb_gc_get_objspace(), src);
MEMZERO((void *)src, char, rb_gc_obj_slot_size(src));
RBASIC(src)->flags = T_OBJECT | FL_FREEZE; // Avoid mutations using bind_call, etc.
}
void
rb_gc_mark_children(void *objspace, VALUE obj)
{

View File

@ -183,7 +183,6 @@ struct rb_gc_object_metadata_entry {
/* gc.c */
RUBY_ATTR_MALLOC void *ruby_mimmalloc(size_t size);
RUBY_ATTR_MALLOC void *ruby_mimcalloc(size_t num, size_t size);
void rb_gc_ractor_moved(VALUE dest, VALUE src);
void ruby_mimfree(void *ptr);
void rb_gc_prepare_heap(void);
void rb_objspace_set_event_hook(const rb_event_flag_t event);

View File

@ -3539,6 +3539,19 @@ rb_obj_traverse_replace(VALUE obj,
}
}
static const bool wb_protected_types[RUBY_T_MASK] = {
[T_OBJECT] = RGENGC_WB_PROTECTED_OBJECT,
[T_HASH] = RGENGC_WB_PROTECTED_HASH,
[T_ARRAY] = RGENGC_WB_PROTECTED_ARRAY,
[T_STRING] = RGENGC_WB_PROTECTED_STRING,
[T_STRUCT] = RGENGC_WB_PROTECTED_STRUCT,
[T_COMPLEX] = RGENGC_WB_PROTECTED_COMPLEX,
[T_REGEXP] = RGENGC_WB_PROTECTED_REGEXP,
[T_MATCH] = RGENGC_WB_PROTECTED_MATCH,
[T_FLOAT] = RGENGC_WB_PROTECTED_FLOAT,
[T_RATIONAL] = RGENGC_WB_PROTECTED_RATIONAL,
};
static enum obj_traverse_iterator_result
move_enter(VALUE obj, struct obj_traverse_replace_data *data)
{
@ -3547,7 +3560,10 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data)
return traverse_skip;
}
else {
data->replacement = rb_obj_clone(obj);
VALUE type = RB_BUILTIN_TYPE(obj);
type |= wb_protected_types[type] ? FL_WB_PROTECTED : 0;
NEWOBJ_OF(moved, struct RBasic, 0, type, rb_gc_obj_slot_size(obj), 0);
data->replacement = (VALUE)moved;
return traverse_cont;
}
}
@ -3555,7 +3571,20 @@ move_enter(VALUE obj, struct obj_traverse_replace_data *data)
static enum obj_traverse_iterator_result
move_leave(VALUE obj, struct obj_traverse_replace_data *data)
{
rb_gc_ractor_moved(data->replacement, obj);
size_t size = rb_gc_obj_slot_size(obj);
memcpy((void *)data->replacement, (void *)obj, size);
FL_UNSET_RAW(data->replacement, FL_SEEN_OBJ_ID);
void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c
if (UNLIKELY(FL_TEST_RAW(obj, FL_EXIVAR))) {
rb_replace_generic_ivar(data->replacement, obj);
}
// Avoid mutations using bind_call, etc.
// We keep FL_SEEN_OBJ_ID so GC later clean the obj_id_table.
MEMZERO((char *)obj + sizeof(struct RBasic), char, size - sizeof(struct RBasic));
RBASIC(obj)->flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_SEEN_OBJ_ID);
RBASIC_SET_CLASS_RAW(obj, rb_cRactorMovedObject);
return traverse_cont;
}

View File

@ -2157,6 +2157,27 @@ rb_copy_generic_ivar(VALUE clone, VALUE obj)
}
}
void
rb_replace_generic_ivar(VALUE clone, VALUE obj)
{
RUBY_ASSERT(FL_TEST(obj, FL_EXIVAR));
RB_VM_LOCK_ENTER();
{
st_data_t ivtbl, obj_data = (st_data_t)obj;
if (st_delete(generic_iv_tbl_, &obj_data, &ivtbl)) {
FL_UNSET_RAW(obj, FL_EXIVAR);
st_insert(generic_iv_tbl_, (st_data_t)clone, ivtbl);
FL_SET_RAW(clone, FL_EXIVAR);
}
else {
rb_bug("unreachable");
}
}
RB_VM_LOCK_LEAVE();
}
void
rb_ivar_foreach(VALUE obj, rb_ivar_foreach_callback_func *func, st_data_t arg)
{