Fix memory leak in cloning complex imemo_fields

When we clone a complex imemo_fields, it calls creates the imemo_fields
using rb_imemo_fields_new_complex, which allocates and initializes a new
st_table. However, st_replace will directly replace any exisiting fields
in the st_table, causing it to leak.

For example, this script demonstrates the leak:

    obj = Class.new
    8.times do |i|
      obj.instance_variable_set(:"@test#{i}", nil)
      obj.remove_instance_variable(:"@test#{i}")
    end

    obj.instance_variable_set(:"@test", 1)

    10.times do
      100_000.times do
        obj.dup
      end

      puts `ps -o rss= -p #{$$}`
    end

Before:

    26320
    39296
    52320
    63136
    75520
    87008
    97856
    114800
    120864
    133504

After:

    16288
    20112
    20416
    20720
    20800
    20864
    21184
    21424
    21904
    21904
This commit is contained in:
Peter Zhu 2025-09-19 10:37:40 -04:00
parent 5179b7fb3f
commit 3ec597f619

View File

@ -167,11 +167,14 @@ rb_imemo_fields_clone(VALUE fields_obj)
VALUE clone;
if (rb_shape_too_complex_p(shape_id)) {
clone = rb_imemo_fields_new_complex(rb_imemo_fields_owner(fields_obj), 0);
RBASIC_SET_SHAPE_ID(clone, shape_id);
st_table *src_table = rb_imemo_fields_complex_tbl(fields_obj);
st_table *dest_table = rb_imemo_fields_complex_tbl(clone);
st_table *dest_table = xcalloc(1, sizeof(st_table));
clone = rb_imemo_fields_new_complex_tbl(rb_imemo_fields_owner(fields_obj), dest_table);
st_replace(dest_table, src_table);
RBASIC_SET_SHAPE_ID(clone, shape_id);
st_foreach(dest_table, imemo_fields_complex_wb_i, (st_data_t)clone);
}
else {