diff options
Diffstat (limited to 'ext/objspace/object_tracing.c')
| -rw-r--r-- | ext/objspace/object_tracing.c | 303 |
1 files changed, 206 insertions, 97 deletions
diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c index d3169c6c48..74d793a6e2 100644 --- a/ext/objspace/object_tracing.c +++ b/ext/objspace/object_tracing.c @@ -14,6 +14,7 @@ **********************************************************************/ #include "internal.h" +#include "internal/gc.h" #include "ruby/debug.h" #include "objspace.h" @@ -21,7 +22,6 @@ struct traceobj_arg { int running; int keep_remains; VALUE newobj_trace; - VALUE freeobj_trace; st_table *object_table; /* obj (VALUE) -> allocation_info */ st_table *str_table; /* cstr -> refcount */ struct traceobj_arg *prev_traceobj_arg; @@ -31,40 +31,50 @@ static const char * make_unique_str(st_table *tbl, const char *str, long len) { if (!str) { - return NULL; + return NULL; } else { - st_data_t n; - char *result; - - if (st_lookup(tbl, (st_data_t)str, &n)) { - st_insert(tbl, (st_data_t)str, n+1); - st_get_key(tbl, (st_data_t)str, (st_data_t *)&result); - } - else { - result = (char *)ruby_xmalloc(len+1); - strncpy(result, str, len); - result[len] = 0; - st_add_direct(tbl, (st_data_t)result, 1); - } - return result; + st_data_t n; + char *result; + + if (st_lookup(tbl, (st_data_t)str, &n)) { + st_insert(tbl, (st_data_t)str, n+1); + st_get_key(tbl, (st_data_t)str, &n); + result = (char *)n; + } + else { + result = (char *)ruby_xmalloc(len+1); + strncpy(result, str, len); + result[len] = 0; + st_add_direct(tbl, (st_data_t)result, 1); + } + return result; } } +static int +delete_unique_str_dec(st_data_t *key, st_data_t *value, st_data_t arg, int existing) +{ + assert(existing); + *value = arg; + return ST_CONTINUE; +} + static void delete_unique_str(st_table *tbl, const char *str) { if (str) { - st_data_t n; - - st_lookup(tbl, (st_data_t)str, &n); - if (n == 1) { - st_delete(tbl, (st_data_t *)&str, 0); - ruby_xfree((char *)str); - } - else { - st_insert(tbl, (st_data_t)str, n-1); - } + st_data_t n; + + st_lookup(tbl, (st_data_t)str, &n); + if (n == 1) { + n = (st_data_t)str; + st_delete(tbl, &n, 0); + ruby_xfree((char *)n); + } + else { + st_update(tbl, (st_data_t)str, delete_unique_str_dec, (st_data_t)(n-1)); + } } } @@ -80,21 +90,21 @@ newobj_i(VALUE tpval, void *data) VALUE klass = rb_tracearg_defined_class(tparg); struct allocation_info *info; const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0; - VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil; + VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_mod_name(klass) : Qnil; const char *class_path_cstr = RTEST(class_path) ? make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0; - - if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) { - if (arg->keep_remains) { - if (info->living) { - /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */ - } - } - /* reuse info */ - delete_unique_str(arg->str_table, info->path); - delete_unique_str(arg->str_table, info->class_path); + st_data_t v; + + if (st_lookup(arg->object_table, (st_data_t)obj, &v)) { + /* keep_remains kept this slot's entry after its object was freed. The + * allocator has now reused that address, so recycle the dead entry's + * info. A living entry here would mean two live objects at one address. */ + info = (struct allocation_info *)v; + assert(!info->living); + delete_unique_str(arg->str_table, info->path); + delete_unique_str(arg->str_table, info->class_path); } else { - info = (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info)); + info = (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info)); } info->living = 1; info->flags = RBASIC(obj)->flags; @@ -108,41 +118,133 @@ newobj_i(VALUE tpval, void *data) st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info); } +static int +free_keys_i(st_data_t key, st_data_t value, st_data_t data) +{ + ruby_xfree((void *)key); + return ST_CONTINUE; +} + +static int +free_values_i(st_data_t key, st_data_t value, st_data_t data) +{ + ruby_xfree((void *)value); + return ST_CONTINUE; +} + +static void +allocation_info_tracer_mark(void *ptr) +{ + struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; + rb_gc_mark(trace_arg->newobj_trace); +} + static void -freeobj_i(VALUE tpval, void *data) +allocation_info_tracer_free(void *ptr) +{ + struct traceobj_arg *arg = (struct traceobj_arg *)ptr; + /* clear tables */ + st_foreach(arg->object_table, free_values_i, 0); + st_free_table(arg->object_table); + st_foreach(arg->str_table, free_keys_i, 0); + st_free_table(arg->str_table); + xfree(arg); +} + +static size_t +allocation_info_tracer_memsize(const void *ptr) +{ + size_t size; + struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; + size = sizeof(*trace_arg); + size += st_memsize(trace_arg->object_table); + size += st_memsize(trace_arg->str_table); + return size; +} + +static int +allocation_info_tracer_weak_reference_i(st_data_t key, st_data_t value, st_data_t data) { struct traceobj_arg *arg = (struct traceobj_arg *)data; - rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); - VALUE obj = rb_tracearg_object(tparg); - struct allocation_info *info; + struct allocation_info *info = (struct allocation_info *)value; - if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) { - if (arg->keep_remains) { - info->living = 0; - } - else { - st_delete(arg->object_table, (st_data_t *)&obj, (st_data_t *)&info); - delete_unique_str(arg->str_table, info->path); - delete_unique_str(arg->str_table, info->class_path); - ruby_xfree(info); - } + if (rb_gc_handle_weak_references_alive_p((VALUE)key)) { + return ST_CONTINUE; + } + + /* Object was collected. keep_remains keeps the dead entry for reporting. */ + if (arg->keep_remains) { + info->living = 0; + return ST_CONTINUE; + } + else { + delete_unique_str(arg->str_table, info->path); + delete_unique_str(arg->str_table, info->class_path); + ruby_xfree(info); + return ST_DELETE; } } -static int -free_keys_i(st_data_t key, st_data_t value, void *data) +static void +allocation_info_tracer_weak_reference(void *ptr) { - ruby_xfree((void *)key); - return ST_CONTINUE; + struct traceobj_arg *arg = (struct traceobj_arg *)ptr; + + st_foreach(arg->object_table, allocation_info_tracer_weak_reference_i, (st_data_t)arg); } static int -free_values_i(st_data_t key, st_data_t value, void *data) +allocation_info_tracer_compact_update_object_table_i(st_data_t key, st_data_t value, st_data_t data) { - ruby_xfree((void *)value); + st_table *table = (st_table *)data; + struct allocation_info *info = (struct allocation_info *)value; + + /* In keep_remains mode the table keeps entries for freed objects. Their keys + * are dangling, so skip them instead of passing them to rb_gc_location. */ + if (!info->living) { + return ST_CONTINUE; + } + + if (key != rb_gc_location(key)) { + DURING_GC_COULD_MALLOC_REGION_START(); + { + st_insert(table, rb_gc_location(key), value); + } + DURING_GC_COULD_MALLOC_REGION_END(); + + return ST_DELETE; + } + return ST_CONTINUE; } +static void +allocation_info_tracer_compact(void *ptr) +{ + struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; + + if (trace_arg->object_table && + st_foreach( + trace_arg->object_table, + allocation_info_tracer_compact_update_object_table_i, + (st_data_t)trace_arg->object_table)) { + rb_raise(rb_eRuntimeError, "hash modified during iteration"); + } +} + +static const rb_data_type_t allocation_info_tracer_type = { + "ObjectTracing/allocation_info_tracer", + { + allocation_info_tracer_mark, + allocation_info_tracer_free, /* Never called because global */ + allocation_info_tracer_memsize, + allocation_info_tracer_compact, + allocation_info_tracer_weak_reference, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE traceobj_arg; static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */ static int tmp_keep_remains; /* TODO: Do not use global variables */ @@ -150,13 +252,16 @@ static struct traceobj_arg * get_traceobj_arg(void) { if (tmp_trace_arg == 0) { - tmp_trace_arg = ALLOC_N(struct traceobj_arg, 1); - tmp_trace_arg->running = 0; - tmp_trace_arg->keep_remains = tmp_keep_remains; - tmp_trace_arg->newobj_trace = 0; - tmp_trace_arg->freeobj_trace = 0; - tmp_trace_arg->object_table = st_init_numtable(); - tmp_trace_arg->str_table = st_init_strtable(); + VALUE obj = TypedData_Make_Struct(rb_cObject, struct traceobj_arg, &allocation_info_tracer_type, tmp_trace_arg); + traceobj_arg = obj; + rb_gc_register_mark_object(traceobj_arg); + tmp_trace_arg->running = 0; + tmp_trace_arg->keep_remains = tmp_keep_remains; + tmp_trace_arg->newobj_trace = 0; + tmp_trace_arg->object_table = st_init_numtable(); + tmp_trace_arg->str_table = st_init_strtable(); + + rb_gc_declare_weak_references(obj); } return tmp_trace_arg; } @@ -173,15 +278,13 @@ trace_object_allocations_start(VALUE self) struct traceobj_arg *arg = get_traceobj_arg(); if (arg->running++ > 0) { - /* do nothing */ + /* do nothing */ } else { - if (arg->newobj_trace == 0) { - arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg); - arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg); - } - rb_tracepoint_enable(arg->newobj_trace); - rb_tracepoint_enable(arg->freeobj_trace); + if (arg->newobj_trace == 0) { + arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg); + } + rb_tracepoint_enable(arg->newobj_trace); } return Qnil; @@ -202,14 +305,13 @@ trace_object_allocations_stop(VALUE self) struct traceobj_arg *arg = get_traceobj_arg(); if (arg->running > 0) { - arg->running--; + arg->running--; } if (arg->running == 0) { - rb_tracepoint_disable(arg->newobj_trace); - rb_tracepoint_disable(arg->freeobj_trace); - arg->newobj_trace = 0; - arg->freeobj_trace = 0; + if (arg->newobj_trace != 0) { + rb_tracepoint_disable(arg->newobj_trace); + } } return Qnil; @@ -287,8 +389,8 @@ object_allocations_reporter_i(st_data_t key, st_data_t val, st_data_t ptr) else fprintf(out, "C: %p", (void *)info->klass); fprintf(out, "@%s:%lu", info->path ? info->path : "", info->line); if (!NIL_P(info->mid)) { - VALUE m = rb_sym2str(info->mid); - fprintf(out, " (%s)", RSTRING_PTR(m)); + VALUE m = rb_sym2str(info->mid); + fprintf(out, " (%s)", RSTRING_PTR(m)); } fprintf(out, ")\n"); @@ -300,18 +402,25 @@ object_allocations_reporter(FILE *out, void *ptr) { fprintf(out, "== object_allocations_reporter: START\n"); if (tmp_trace_arg) { - st_foreach(tmp_trace_arg->object_table, object_allocations_reporter_i, (st_data_t)out); + st_foreach(tmp_trace_arg->object_table, object_allocations_reporter_i, (st_data_t)out); } fprintf(out, "== object_allocations_reporter: END\n"); } +/* + * call-seq: trace_object_allocations_debug_start + * + * Starts tracing object allocations for GC debugging. + * If you encounter the BUG "... is T_NONE" (and so on) on your + * application, please try this method at the beginning of your app. + */ static VALUE trace_object_allocations_debug_start(VALUE self) { tmp_keep_remains = 1; if (object_allocations_reporter_registered == 0) { - object_allocations_reporter_registered = 1; - rb_bug_reporter_add(object_allocations_reporter, 0); + object_allocations_reporter_registered = 1; + rb_bug_reporter_add(object_allocations_reporter, 0); } return trace_object_allocations_start(self); @@ -321,10 +430,10 @@ static struct allocation_info * lookup_allocation_info(VALUE obj) { if (tmp_trace_arg) { - struct allocation_info *info; - if (st_lookup(tmp_trace_arg->object_table, obj, (st_data_t *)&info)) { - return info; - } + st_data_t info; + if (st_lookup(tmp_trace_arg->object_table, obj, &info)) { + return (struct allocation_info *)info; + } } return NULL; } @@ -348,15 +457,15 @@ allocation_sourcefile(VALUE self, VALUE obj) struct allocation_info *info = lookup_allocation_info(obj); if (info && info->path) { - return rb_str_new2(info->path); + return rb_str_new2(info->path); } else { - return Qnil; + return Qnil; } } /* - * call-seq: allocation_sourceline(object) -> string + * call-seq: allocation_sourceline(object) -> integer * * Returns the original line from source for from the given +object+. * @@ -368,10 +477,10 @@ allocation_sourceline(VALUE self, VALUE obj) struct allocation_info *info = lookup_allocation_info(obj); if (info) { - return INT2FIX(info->line); + return INT2FIX(info->line); } else { - return Qnil; + return Qnil; } } @@ -399,10 +508,10 @@ allocation_class_path(VALUE self, VALUE obj) struct allocation_info *info = lookup_allocation_info(obj); if (info && info->class_path) { - return rb_str_new2(info->class_path); + return rb_str_new2(info->class_path); } else { - return Qnil; + return Qnil; } } @@ -431,10 +540,10 @@ allocation_method_id(VALUE self, VALUE obj) { struct allocation_info *info = lookup_allocation_info(obj); if (info) { - return info->mid; + return info->mid; } else { - return Qnil; + return Qnil; } } @@ -463,10 +572,10 @@ allocation_generation(VALUE self, VALUE obj) { struct allocation_info *info = lookup_allocation_info(obj); if (info) { - return SIZET2NUM(info->generation); + return SIZET2NUM(info->generation); } else { - return Qnil; + return Qnil; } } |
