1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

class.c: calculate the length of Class.descendants in advance

GC must not be triggered during callback of rb_class_foreach_subclass.
To prevent GC, we can not use rb_ary_push. Instead, this changeset calls
rb_class_foreach_subclass twice: first counts the subclasses, then
allocates a buffer (which may cause GC and reduce subclasses, but not
increase), and finally stores the subclasses to the buffer.

[Bug #18282] [Feature #14394]
This commit is contained in:
Yusuke Endoh 2021-11-02 19:23:36 +09:00
parent 3ff0a0b40c
commit 428227472f
Notes: git 2021-11-09 16:11:33 +09:00
2 changed files with 36 additions and 7 deletions

37
class.c
View file

@ -124,6 +124,8 @@ rb_class_foreach_subclass(VALUE klass, void (*f)(VALUE, VALUE), VALUE arg)
while (cur) {
VALUE curklass = cur->klass;
cur = cur->next;
// do not trigger GC during f, otherwise the cur will become
// a dangling pointer if the subclass is collected
f(curklass, arg);
}
}
@ -1334,13 +1336,24 @@ rb_mod_ancestors(VALUE mod)
return ary;
}
static void
class_descendants_recursive(VALUE klass, VALUE ary)
struct subclass_traverse_data
{
VALUE *buffer;
long count;
};
static void
class_descendants_recursive(VALUE klass, VALUE v)
{
struct subclass_traverse_data *data = (struct subclass_traverse_data *) v;
if (BUILTIN_TYPE(klass) == T_CLASS && !FL_TEST(klass, FL_SINGLETON)) {
rb_ary_push(ary, klass);
if (data->buffer) {
data->buffer[data->count] = klass;
}
data->count++;
}
rb_class_foreach_subclass(klass, class_descendants_recursive, ary);
rb_class_foreach_subclass(klass, class_descendants_recursive, v);
}
/*
@ -1364,9 +1377,19 @@ class_descendants_recursive(VALUE klass, VALUE ary)
VALUE
rb_class_descendants(VALUE klass)
{
VALUE ary = rb_ary_new();
rb_class_foreach_subclass(klass, class_descendants_recursive, ary);
return ary;
struct subclass_traverse_data data = { NULL, 0 };
// estimate the count of subclasses
rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
// this allocation may cause GC which may reduce the subclasses
data.buffer = ALLOCA_N(VALUE, data.count);
data.count = 0;
// enumerate subclasses
rb_class_foreach_subclass(klass, class_descendants_recursive, (VALUE) &data);
return rb_ary_new_from_values(data.count, data.buffer);
}
static void

View file

@ -755,4 +755,10 @@ class TestClass < Test::Unit::TestCase
assert_include(object_descendants, sc)
assert_include(object_descendants, ssc)
end
def test_descendants_gc
c = Class.new
100000.times { Class.new(c) }
assert(c.descendants.size <= 100000)
end
end