Skip to content

Commit

Permalink
* ext/objspace/objspace.c: add ObjectSpace#reachable_objects_from.
Browse files Browse the repository at this point in the history
  This method returns an array of objects referenced by given object.
  If given object is special objects such as true/false/nil/Fixnum etc
  then it returns nil. See rdoc for details.
  [ruby-core:39772]
* test/objspace/test_objspace.rb: add a test for this method.
* gc.c: add rb_objspace_reachable_objects_from().
  To make this function, add several member `mark_func_data'
  to rb_objspace_t.  If mark_func_data is not null, then
  gc_mark() calls mark_func_data::mark_func.
* gc.h: export rb_objspace_reachable_objects_from().



git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@37094 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
  • Loading branch information
ko1 committed Oct 5, 2012
1 parent 7cc6bfa commit e03d6d9
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 31 deletions.
17 changes: 17 additions & 0 deletions ChangeLog
@@ -1,3 +1,20 @@
Fri Oct 05 15:26:18 2012 Koichi Sasada <ko1@atdot.net>

* ext/objspace/objspace.c: add ObjectSpace#reachable_objects_from.
This method returns an array of objects referenced by given object.
If given object is special objects such as true/false/nil/Fixnum etc
then it returns nil. See rdoc for details.
[ruby-core:39772]

* test/objspace/test_objspace.rb: add a test for this method.

* gc.c: add rb_objspace_reachable_objects_from().
To make this function, add several member `mark_func_data'
to rb_objspace_t. If mark_func_data is not null, then
gc_mark() calls mark_func_data::mark_func.

* gc.h: export rb_objspace_reachable_objects_from().

Fri Oct 5 16:04:33 2012 Narihiro Nakamura <authornari@gmail.com>

* gc.c (chain_finalized_object): remove to check a mark flag since
Expand Down
47 changes: 45 additions & 2 deletions ext/objspace/objspace.c
Expand Up @@ -37,6 +37,7 @@ size_t rb_ary_memsize(VALUE);
size_t rb_io_memsize(const rb_io_t *);
size_t rb_generic_ivar_memsize(VALUE);
size_t rb_objspace_data_type_memsize(VALUE obj);
VALUE rb_objspace_reachable_objects_from(VALUE obj);

static size_t
memsize_of(VALUE obj)
Expand Down Expand Up @@ -626,6 +627,47 @@ count_tdata_objects(int argc, VALUE *argv, VALUE self)
return hash;
}

/*
* call-seq:
* ObjectSpace.reachable_objects_from(obj) -> array or nil
*
* [MRI specific feature] Return all reachable objects from `obj'.
*
* This method returns all reachable objects from `obj'.
* If `obj' has references two or more references to same object `x',
* them returned array only include one `x' object.
* If `obj' is non-markable (non-heap management) object such as
* true, false, nil, symbols and Fixnums (and Flonum) them it simply
* returns nil.
*
* With this method, you can find memory leaks.
*
* This method is not expected to work except C Ruby.
*
* Example:
* ObjectSpace.reachable_objects_from(['a', 'b', 'c'])
* #=> [Array, 'a', 'b', 'c']
*
* ObjectSpace.reachable_objects_from(['a', 'a', 'a'])
* #=> [Array, 'a', 'a', 'a'] # all 'a' strings have different object id
*
* ObjectSpace.reachable_objects_from([v = 'a', v, v])
* #=> [Array, 'a']
*
* ObjectSpace.reachable_objects_from(1)
* #=> nil # 1 is not markable (heap managed) object
*
* Limitation: Current implementation can't acquire internal objects.
* This means that you can't acquire complete object graph
* (heap snapshot). This is future work.
*
*/
static VALUE
reachable_objects_from(VALUE self, VALUE obj)
{
return rb_objspace_reachable_objects_from(obj);
}

/* objspace library extends ObjectSpace module and add several
* methods to get internal statistic information about
* object/memory management.
Expand All @@ -642,10 +684,11 @@ Init_objspace(void)
VALUE rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));

rb_define_module_function(rb_mObjSpace, "memsize_of", memsize_of_m, 1);
rb_define_module_function(rb_mObjSpace, "memsize_of_all",
memsize_of_all_m, -1);
rb_define_module_function(rb_mObjSpace, "memsize_of_all", memsize_of_all_m, -1);

rb_define_module_function(rb_mObjSpace, "count_objects_size", count_objects_size, -1);
rb_define_module_function(rb_mObjSpace, "count_nodes", count_nodes, -1);
rb_define_module_function(rb_mObjSpace, "count_tdata_objects", count_tdata_objects, -1);

rb_define_module_function(rb_mObjSpace, "reachable_objects_from", reachable_objects_from, 1);
}
140 changes: 111 additions & 29 deletions gc.c
Expand Up @@ -273,6 +273,11 @@ typedef struct rb_objspace {
struct gc_list *global_list;
size_t count;
int gc_stress;

struct mark_func_data_struct {
VALUE data;
void (*mark_func)(struct rb_objspace *objspace, VALUE v);
} *mark_func_data;
} rb_objspace_t;

#if defined(ENABLE_VM_OBJSPACE) && ENABLE_VM_OBJSPACE
Expand Down Expand Up @@ -1137,31 +1142,41 @@ struct os_each_struct {
VALUE of;
};

static int
internal_object_p(VALUE obj)
{
RVALUE *p = (RVALUE *)obj;

if (p->as.basic.flags) {
switch (BUILTIN_TYPE(p)) {
case T_NONE:
case T_ICLASS:
case T_NODE:
case T_ZOMBIE:
break;
case T_CLASS:
if (FL_TEST(p, FL_SINGLETON))
break;
default:
if (!p->as.basic.klass) break;
return 0;
}
}
return 1;
}

static int
os_obj_of_i(void *vstart, void *vend, size_t stride, void *data)
{
struct os_each_struct *oes = (struct os_each_struct *)data;
RVALUE *p = (RVALUE *)vstart, *pend = (RVALUE *)vend;
volatile VALUE v;

for (; p != pend; p++) {
if (p->as.basic.flags) {
switch (BUILTIN_TYPE(p)) {
case T_NONE:
case T_ICLASS:
case T_NODE:
case T_ZOMBIE:
continue;
case T_CLASS:
if (FL_TEST(p, FL_SINGLETON))
continue;
default:
if (!p->as.basic.klass) continue;
v = (VALUE)p;
if (!oes->of || rb_obj_is_kind_of(v, oes->of)) {
rb_yield(v);
oes->num++;
}
volatile VALUE v = (VALUE)p;
if (!internal_object_p(v)) {
if (!oes->of || rb_obj_is_kind_of(v, oes->of)) {
rb_yield(v);
oes->num++;
}
}
}
Expand Down Expand Up @@ -2521,17 +2536,31 @@ gc_mark_ptr(rb_objspace_t *objspace, VALUE ptr)
return 1;
}

static int
markable_object_p(rb_objspace_t *objspace, VALUE ptr)
{
register RVALUE *obj = RANY(ptr);

if (rb_special_const_p(ptr)) return 0; /* special const not marked */
if (obj->as.basic.flags == 0) return 0 ; /* free cell */

return 1;
}

static void
gc_mark(rb_objspace_t *objspace, VALUE ptr)
{
register RVALUE *obj;

obj = RANY(ptr);
if (rb_special_const_p(ptr)) return; /* special const not marked */
if (obj->as.basic.flags == 0) return; /* free cell */
if (!gc_mark_ptr(objspace, ptr)) return; /* already marked */
if (!markable_object_p(objspace, ptr)) {
return;
}

push_mark_stack(&objspace->mark_stack, ptr);
if (LIKELY(objspace->mark_func_data == 0)) {
if (!gc_mark_ptr(objspace, ptr)) return; /* already marked */
push_mark_stack(&objspace->mark_stack, ptr);
}
else {
objspace->mark_func_data->mark_func(objspace, ptr);
}
}

void
Expand All @@ -2548,10 +2577,16 @@ gc_mark_children(rb_objspace_t *objspace, VALUE ptr)
goto marking; /* skip */

again:
obj = RANY(ptr);
if (rb_special_const_p(ptr)) return; /* special const not marked */
if (obj->as.basic.flags == 0) return; /* free cell */
if (!gc_mark_ptr(objspace, ptr)) return; /* already marked */
if (LIKELY(objspace->mark_func_data == 0)) {
obj = RANY(ptr);
if (rb_special_const_p(ptr)) return; /* special const not marked */
if (obj->as.basic.flags == 0) return; /* free cell */
if (!gc_mark_ptr(objspace, ptr)) return; /* already marked */
}
else {
gc_mark(objspace, ptr);
return;
}

marking:
if (FL_TEST(obj, FL_EXIVAR)) {
Expand Down Expand Up @@ -2953,6 +2988,8 @@ rb_gc_unregister_address(VALUE *addr)
static int
garbage_collect(rb_objspace_t *objspace)
{
struct mark_func_data_struct *prev_mark_func_data;

if (GC_NOTIFY) printf("start garbage_collect()\n");

if (!heaps) {
Expand All @@ -2964,6 +3001,9 @@ garbage_collect(rb_objspace_t *objspace)

gc_prof_timer_start(objspace);

prev_mark_func_data = objspace->mark_func_data;
objspace->mark_func_data = 0;

rest_sweep(objspace);

during_gc++;
Expand All @@ -2973,6 +3013,8 @@ garbage_collect(rb_objspace_t *objspace)
gc_sweep(objspace);
gc_prof_sweep_timer_stop(objspace);

objspace->mark_func_data = prev_mark_func_data;

gc_prof_timer_stop(objspace, Qtrue);
if (GC_NOTIFY) printf("end garbage_collect()\n");
return TRUE;
Expand Down Expand Up @@ -3241,6 +3283,46 @@ rb_gc_set_params(void)
}
}

static void
collect_refs(rb_objspace_t *objspace, VALUE obj)
{
if (markable_object_p(objspace, obj) && !internal_object_p(obj)) {
st_insert((st_table *)objspace->mark_func_data->data, obj, Qtrue);
}
}

static int
collect_keys(st_data_t key, st_data_t value, st_data_t data)
{
VALUE ary = (VALUE)data;
rb_ary_push(ary, (VALUE)key);
return ST_CONTINUE;
}

VALUE
rb_objspace_reachable_objects_from(VALUE obj)
{
rb_objspace_t *objspace = &rb_objspace;

if (markable_object_p(objspace, obj)) {
st_table *refs = st_init_numtable();
struct mark_func_data_struct mfd;
VALUE ret = rb_ary_new();
mfd.mark_func = collect_refs;
mfd.data = (VALUE)refs;
objspace->mark_func_data = &mfd;

gc_mark_children(objspace, obj);

objspace->mark_func_data = 0;
st_foreach(refs, collect_keys, (st_data_t)ret);
return ret;
}
else {
return Qnil;
}
}

/*
------------------------ Extended allocator ------------------------
*/
Expand Down
3 changes: 3 additions & 0 deletions gc.h
Expand Up @@ -87,7 +87,10 @@ int ruby_get_stack_grow_direction(volatile VALUE *addr);
#pragma GCC visibility push(default)
#endif

/* exports for objspace module */
size_t rb_objspace_data_type_memsize(VALUE obj);
VALUE rb_objspace_reachable_objects_from(VALUE obj);

void rb_objspace_each_objects(
int (*callback)(void *start, void *end, size_t stride, void *data),
void *data);
Expand Down
26 changes: 26 additions & 0 deletions test/objspace/test_objspace.rb
Expand Up @@ -68,4 +68,30 @@ def test_count_tdata_objects
ObjectSpace.count_tdata_objects(arg)
assert_equal(false, arg.empty?)
end

def test_reachable_objects_from
assert_equal(nil, ObjectSpace.reachable_objects_from(nil))
assert_equal([Array, 'a', 'b', 'c'], ObjectSpace.reachable_objects_from(['a', 'b', 'c']))

assert_equal([Array, 'a', 'a', 'a'], ObjectSpace.reachable_objects_from(['a', 'a', 'a']))
assert_equal([Array, 'a', 'a'], ObjectSpace.reachable_objects_from(['a', v = 'a', v]))
assert_equal([Array, 'a'], ObjectSpace.reachable_objects_from([v = 'a', v, v]))

long_ary = Array.new(1_000){''}
max = 0

ObjectSpace.each_object{|o|
refs = ObjectSpace.reachable_objects_from(o)
max = [refs.size, max].max

unless refs.nil?
refs.each{|ro|
# check this referenced object is not internal object
assert_equal(false, ro.nil?)
}
end
}
STDERR.puts max
assert_equal(true, max >= 1_001) # 1000 elems + Array class
end
end

0 comments on commit e03d6d9

Please sign in to comment.