From: Tanaka Akira Date: 2005-01-21T20:50:05+09:00 Subject: Re: 1.8.2 - conituations memory leak fixed? In article <1106173537.085688.40700@z14g2000cwz.googlegroups.com>, "jc" writes: > Tanaka Akira posted a patch to record GC-related information: > > * the total number of GC invocation > * the number of GC invocation, when an object is collected > * the location where the last GC is yielded > This patch might help you if you are interested in GC internals. It was not for memory leak. It was intended to investigate a wrongly collected object. The memory leak is opposite situation: some objects are not collected. So, I updated the patch to investigate a live object. The attached patch implements GC.trace_object which makes Ruby to report why a registered object is not collected. % ./ruby -ve ' k = nil 5.times { callcc {|k| } p k GC.trace_object(k) GC.start } '|&uniq # gc_mark(1): Scope (0x401d8d34) -> Data (0x401c75e8) gc_mark(1): root (0x08078711,0x08078f4e) -> Data (0x401c75e8) # gc_mark(2): Scope (0x401d8d34) -> Data (0x401c78a4) gc_mark(2): Data (0x401c78a4) -> Data (0x401c75e8) gc_mark(2): root (0x08078711,0x08078f4e) -> Data (0x401c78a4) # gc_mark(3): Scope (0x401d8d34) -> Data (0x401c7804) gc_mark(3): Data (0x401c7804) -> Data (0x401c78a4) gc_mark(3): Data (0x401c78a4) -> Data (0x401c75e8) gc_mark(3): Data (0x401c7804) -> Data (0x401c78a4) gc_mark(3): root (0x08078711,0x08078f4e) -> Data (0x401c7804) # gc_mark(4): Scope (0x401d8d34) -> Data (0x401c7728) gc_mark(4): Data (0x401c7728) -> Data (0x401c7804) gc_mark(4): Data (0x401c7804) -> Data (0x401c78a4) gc_mark(4): Data (0x401c78a4) -> Data (0x401c75e8) gc_mark(4): Data (0x401c7804) -> Data (0x401c78a4) gc_mark(4): Data (0x401c7728) -> Data (0x401c7804) gc_mark(4): root (0x08078711,0x08078f4e) -> Data (0x401c7728) # gc_mark(5): Scope (0x401d8d34) -> Data (0x401c75ac) gc_mark(5): Data (0x401c75ac) -> Data (0x401c7728) gc_mark(5): Data (0x401c7728) -> Data (0x401c7804) gc_mark(5): Data (0x401c7804) -> Data (0x401c78a4) gc_mark(5): Data (0x401c78a4) -> Data (0x401c75e8) gc_mark(5): Data (0x401c7804) -> Data (0x401c78a4) gc_mark(5): Data (0x401c7728) -> Data (0x401c7804) gc_mark(5): Data (0x401c75ac) -> Data (0x401c7728) gc_mark(5): root (0x08078711,0x08078f4e) -> Data (0x401c75ac) ruby 1.9.0 (2005-01-21) [i686-linux] This mean that the Nth continuation refers the N-1th continuation. So the continuations are never collected. I'm not sure that it should be called "memory leak" or not. But it is unpleasant behavior anyway. I have no idea to fix it portably, though. Index: error.c =================================================================== RCS file: /src/ruby/error.c,v retrieving revision 1.103 diff -u -p -r1.103 error.c --- error.c 17 Nov 2004 02:27:37 -0000 1.103 +++ error.c 21 Jan 2005 11:29:24 -0000 @@ -244,6 +244,19 @@ static struct types { {-1, 0} }; +char *rb_object_structure_type(VALUE obj) +{ + struct types *type = builtin_types; + int t = TYPE(obj); + + while (type->type >= 0) { + if (type->type == t) { + return type->name; + } + type++; + } +} + void rb_check_type(x, t) VALUE x; Index: eval.c =================================================================== RCS file: /src/ruby/eval.c,v retrieving revision 1.748 diff -u -p -r1.748 eval.c --- eval.c 5 Jan 2005 03:49:50 -0000 1.748 +++ eval.c 21 Jan 2005 11:29:24 -0000 @@ -3865,7 +3865,7 @@ rb_eval(self, n) break; default: - rb_bug("unknown node type %d", nd_type(node)); + rb_bug("unknown node type %d (0x%lx)", nd_type(node), (long)node); } finish: CHECK_INTS; @@ -5741,8 +5741,8 @@ rb_call(klass, recv, mid, argc, argv, sc struct cache_entry *ent; if (!klass) { - rb_raise(rb_eNotImpError, "method `%s' called on terminated object (0x%lx)", - rb_id2name(mid), recv); + rb_bug("method `%s' called on terminated object (0x%lx)", + rb_id2name(mid), recv); } /* is it in the method cache? */ ent = cache + EXPR1(klass, mid); Index: gc.c =================================================================== RCS file: /src/ruby/gc.c,v retrieving revision 1.195 diff -u -p -r1.195 gc.c --- gc.c 7 Jan 2005 09:05:52 -0000 1.195 +++ gc.c 21 Jan 2005 11:29:24 -0000 @@ -284,6 +284,7 @@ typedef struct RVALUE { struct { unsigned long flags; /* always 0 for freed obj */ struct RVALUE *next; + long gc_count; } free; struct RBasic basic; struct RObject object; @@ -308,6 +309,7 @@ typedef struct RVALUE { #endif } RVALUE; +static long gc_count = 0; static RVALUE *freelist = 0; static RVALUE *deferred_final_list = 0; @@ -701,6 +703,28 @@ rb_gc_mark_maybe(obj) #define GC_LEVEL_MAX 250 +static VALUE gc_mark_base = Qundef; +static st_table *gc_mark_interest = NULL; + +static void gc_collected(RVALUE *p, long gc_count) +{ + VALUE v = (VALUE)p; + p->as.free.gc_count = gc_count; + if (gc_mark_interest && st_lookup(gc_mark_interest, v, NULL)) { + fprintf(stderr, "gc_collected(%ld): 0x%08lx\n", gc_count, v); + st_delete(gc_mark_interest, &v, NULL); + } +} + +static VALUE +gc_trace_object(VALUE self, VALUE obj) +{ + if (!gc_mark_interest) + gc_mark_interest = st_init_numtable(); + st_insert(gc_mark_interest, obj, 1); + return obj; +} + void gc_mark(ptr, lev) VALUE ptr; @@ -708,6 +732,23 @@ gc_mark(ptr, lev) { register RVALUE *obj; + extern char *rb_object_structure_type(VALUE obj); + if (gc_mark_interest && st_lookup(gc_mark_interest, ptr, NULL) && gc_mark_base != ptr) { + if (gc_mark_base == Qundef) { + fprintf(stderr, "gc_mark(%ld): root (0x%08lx,0x%08lx) -> %s (0x%08lx)\n", + gc_count, + (unsigned long)__builtin_return_address(0), + (unsigned long)__builtin_return_address(1), + rb_object_structure_type(ptr), ptr); + } + else { + fprintf(stderr, "gc_mark(%ld): %s (0x%08lx) -> %s (0x%08lx)\n", + gc_count, + rb_object_structure_type(gc_mark_base), gc_mark_base, + rb_object_structure_type(ptr), ptr); + } + } + obj = RANY(ptr); if (rb_special_const_p(ptr)) return; /* special const not marked */ if (obj->as.basic.flags == 0) return; /* free cell */ @@ -737,7 +778,7 @@ rb_gc_mark(ptr) } static void -gc_mark_children(ptr, lev) +gc_mark_children1(ptr, lev) VALUE ptr; int lev; { @@ -994,6 +1035,17 @@ gc_mark_children(ptr, lev) } } +static void +gc_mark_children(ptr, lev) + VALUE ptr; + int lev; +{ + VALUE saved_base = gc_mark_base; + gc_mark_base = ptr; + gc_mark_children1(ptr, lev); + gc_mark_base = saved_base; +} + static void obj_free _((VALUE)); static void @@ -1006,6 +1058,7 @@ finalize_list(p) if (!FL_TEST(p, FL_SINGLETON)) { /* not freeing page */ p->as.free.flags = 0; p->as.free.next = freelist; + gc_collected(p, gc_count); freelist = p; } p = tmp; @@ -1040,6 +1093,7 @@ gc_sweep() unsigned long live = 0; mark_source_filename(ruby_sourcefile); + if (source_filenames) st_foreach(source_filenames, sweep_source_filename, 0); freelist = 0; @@ -1059,11 +1113,13 @@ gc_sweep() if (need_call_final && FL_TEST(p, FL_FINALIZE)) { p->as.free.flags = FL_MARK; /* remain marked */ p->as.free.next = final_list; + gc_collected(p, gc_count); final_list = p; } else { p->as.free.flags = 0; p->as.free.next = freelist; + gc_collected(p, gc_count); freelist = p; } n++; @@ -1115,6 +1171,7 @@ rb_gc_force_recycle(p) { RANY(p)->as.free.flags = 0; RANY(p)->as.free.next = freelist; + gc_collected(RANY(p), -1); freelist = RANY(p); } @@ -1290,6 +1347,11 @@ int rb_setjmp (rb_jmp_buf); #endif /* __human68k__ or DJGPP */ #endif /* __GNUC__ */ +#ifdef __GNUC__ +void *main_return_address; +static void *last_gc_stacktrace[10]; +#endif + static void garbage_collect() { @@ -1312,6 +1374,10 @@ garbage_collect() if (during_gc) return; during_gc++; + gc_count++; + if (gc_count < 0) + gc_count = 0; + init_mark_stack(); /* mark frame stack */ @@ -1401,6 +1467,21 @@ garbage_collect() } } gc_sweep(); + +#ifdef __GNUC__ + memset(last_gc_stacktrace, 0, sizeof(last_gc_stacktrace)); + if ((last_gc_stacktrace[0] = __builtin_return_address(0)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[1] = __builtin_return_address(1)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[2] = __builtin_return_address(2)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[3] = __builtin_return_address(3)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[4] = __builtin_return_address(4)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[5] = __builtin_return_address(5)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[6] = __builtin_return_address(6)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[7] = __builtin_return_address(7)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[8] = __builtin_return_address(8)) == main_return_address) goto gc_stacktrace_done; + if ((last_gc_stacktrace[9] = __builtin_return_address(9)) == main_return_address) goto gc_stacktrace_done; + gc_stacktrace_done: ; +#endif } void @@ -1427,6 +1508,20 @@ rb_gc_start() return Qnil; } +VALUE +numfree() +{ + RVALUE *f = freelist; + long num = 0; + + while (f != 0) { + f = f->as.free.next; + num += 1; + } + printf("numfree:%ld\tmalloc_increase:%lu\tmalloc_limit:%lu\n", num, malloc_increase, malloc_limit); + return Qnil; +} + void ruby_set_stack_size(size) size_t size; @@ -1929,6 +2024,9 @@ Init_GC() rb_define_module_function(rb_mObSpace, "undefine_finalizer", undefine_final, 1); rb_define_module_function(rb_mObSpace, "_id2ref", id2ref, 1); + + rb_define_module_function(rb_mObSpace, "numfree", numfree, 0); + rb_define_module_function(rb_mGC, "trace_object", gc_trace_object, 1); rb_gc_register_address(&rb_mObSpace); rb_global_variable(&finalizers); Index: main.c =================================================================== RCS file: /src/ruby/main.c,v retrieving revision 1.13 diff -u -p -r1.13 main.c --- main.c 23 Jun 2004 12:59:01 -0000 1.13 +++ main.c 21 Jan 2005 11:29:24 -0000 @@ -21,6 +21,10 @@ static void objcdummyfunction( void ) { objc_msgSend(); } #endif +#ifdef __GNUC__ +extern void *main_return_address; +#endif + int main(argc, argv, envp) int argc; @@ -31,6 +35,10 @@ main(argc, argv, envp) #endif #if defined(__MACOS__) && defined(__MWERKS__) argc = ccommand(&argv); +#endif + +#ifdef __GNUC__ + main_return_address = __builtin_return_address(0); #endif ruby_init(); Index: variable.c =================================================================== RCS file: /src/ruby/variable.c,v retrieving revision 1.119 diff -u -p -r1.119 variable.c --- variable.c 10 Jan 2005 14:07:53 -0000 1.119 +++ variable.c 21 Jan 2005 11:29:25 -0000 @@ -467,6 +467,7 @@ mark_global_entry(key, entry) void rb_gc_mark_global_tbl() { + if (rb_global_tbl) st_foreach_safe(rb_global_tbl, mark_global_entry, 0); } -- Tanaka Akira