Skip to content

Commit a4d6719

Browse files
committedNov 16, 2023
Pin object ivars while they are being copied in a hash
When transitioning an object to TOO_COMPLEX we copy all its ivar in a table, but if GC (and compaction) trigger in the middle of the transition, the old `iv_ptr` might still be the canonical ivar list and will be updated by the GC, but the reference we copied in the table will be outdated. So we must pin these reference until they are all copied and the object `iv_ptr` is pointing to the table. To do this I use a custom TypedData that simply mark the new table with `rb_mark_tbl` so references are pinned. .
1 parent 8f1ab1c commit a4d6719

File tree

3 files changed

+36
-4
lines changed

3 files changed

+36
-4
lines changed
 

‎internal/variable.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ VALUE rb_mod_set_temporary_name(VALUE, VALUE);
4747

4848
struct gen_ivtbl;
4949
int rb_gen_ivtbl_get(VALUE obj, ID id, struct gen_ivtbl **ivtbl);
50-
void rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table);
50+
VALUE rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table);
51+
void rb_obj_copy_ivs_to_hash_table_complete(VALUE ivar_pinner);
5152
void rb_obj_convert_to_too_complex(VALUE obj, st_table *table);
5253
void rb_evict_ivars_to_hash(VALUE obj);
5354

‎object.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,9 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj)
326326
shape_to_set_on_dest = rb_shape_rebuild_shape(initial_shape, src_shape);
327327
if (UNLIKELY(rb_shape_id(shape_to_set_on_dest) == OBJ_TOO_COMPLEX_SHAPE_ID)) {
328328
st_table * table = rb_st_init_numtable_with_size(src_num_ivs);
329-
rb_obj_copy_ivs_to_hash_table(obj, table);
329+
VALUE ivar_pinner = rb_obj_copy_ivs_to_hash_table(obj, table);
330330
rb_obj_convert_to_too_complex(dest, table);
331+
rb_obj_copy_ivs_to_hash_table_complete(ivar_pinner);
331332

332333
return;
333334
}

‎variable.c

+32-2
Original file line numberDiff line numberDiff line change
@@ -1422,8 +1422,9 @@ rb_evict_ivars_to_hash(VALUE obj)
14221422
st_table *table = st_init_numtable_with_size(rb_ivar_count(obj));
14231423

14241424
// Evacuate all previous values from shape into id_table
1425-
rb_obj_copy_ivs_to_hash_table(obj, table);
1425+
VALUE ivar_pinner = rb_obj_copy_ivs_to_hash_table(obj, table);
14261426
rb_obj_convert_to_too_complex(obj, table);
1427+
rb_obj_copy_ivs_to_hash_table_complete(ivar_pinner);
14271428

14281429
RUBY_ASSERT(rb_shape_obj_too_complex(obj));
14291430
}
@@ -1647,10 +1648,39 @@ rb_obj_copy_ivs_to_hash_table_i(ID key, VALUE val, st_data_t arg)
16471648
return ST_CONTINUE;
16481649
}
16491650

1650-
void
1651+
static void
1652+
ivar_pinner_mark(void *data) {
1653+
st_table *table = (st_table *)data;
1654+
if (!data) {
1655+
return;
1656+
}
1657+
// rb_mark_tbl pin references
1658+
rb_mark_tbl(table);
1659+
}
1660+
1661+
static const rb_data_type_t ivars_pinner_data_type = {
1662+
.wrap_struct_name = "ivar_pinner",
1663+
.function = {
1664+
.dmark = ivar_pinner_mark,
1665+
.dfree = NULL,
1666+
.dsize = NULL,
1667+
.dcompact = NULL,
1668+
},
1669+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
1670+
};
1671+
1672+
VALUE
16511673
rb_obj_copy_ivs_to_hash_table(VALUE obj, st_table *table)
16521674
{
1675+
VALUE ivar_pinner = TypedData_Wrap_Struct(0, &ivars_pinner_data_type, table);
16531676
rb_ivar_foreach(obj, rb_obj_copy_ivs_to_hash_table_i, (st_data_t)table);
1677+
return ivar_pinner;
1678+
}
1679+
1680+
void
1681+
rb_obj_copy_ivs_to_hash_table_complete(VALUE ivar_pinner)
1682+
{
1683+
RTYPEDDATA_DATA(ivar_pinner) = NULL;
16541684
}
16551685

16561686
static VALUE *

0 commit comments

Comments
 (0)
Failed to load comments.