Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Help! define_method leaking procs...

1 view
Skip to first unread message

Jamis Buck

unread,
Oct 15, 2005, 2:36:05 PM10/15/05
to
A plea for help, here... The rails core team is hacking like mad this
weekend at RubyConf, and my assignment has been to track down and fix
the memory leak in development mode. Here's what is known about this
leak:

1. It only occurs in development mode (production mode does not
exhibit the leak)
2. It only occurs under FastCGI (running under WEBrick does not
exhibit the leak)
3. The leak is due to an accumulation of Proc objects used with
define_method, primarily in ActiveRecord (the define_method's in
question are used to create the dynamic "has_one", "belongs_to",
"has_many", and "habtm" accessors). The leak may also be due to
define_method's used in ActionMailer, as well.

The problem seems strangely nondeterministic. Although it is easily
reproducible, fixing it has been like a nightmare game of whack-a-
mole. I'll add a few lines of code somewhere to test a solution, and
the problem will go away, but moving those lines elsewhere in the
framework (even a line or two up or down) brings the problem back.
And what "fixes" one application's leak may have no effect at all on
another application. There _must_ be a sane solution, however,
because I have verified multiple times (in multiple applications)
that there are no leaks when running the same application under WEBrick.

The leak seems to be on the order of 10 procs per request, per
declared ActiveRecord association. (Basecamp, as an example, leaks
about 200 procs per request in development mode. A minimal app I
wrote to reproduce the problem, with two AR subclasses with one
association each, leaks 20 procs per request.)

I won't spam the lists with additional details, but if anyone is
feeling particularly noble (and brave), please contact me off list
and I'll give you as much information about this as you can handle.
(You'll need to have edge rails and fastcgi working, either with
lighttpd or apache. I can provide you with a sample app to play with,
which requires sqlite.)

- Jamis


Ryan Davis

unread,
Oct 15, 2005, 4:01:58 PM10/15/05
to

On Oct 15, 2005, at 11:36 AM, Jamis Buck wrote:

> A plea for help, here... The rails core team is hacking like mad
> this weekend at RubyConf, and my assignment has been to track down
> and fix the memory leak in development mode. Here's what is known
> about this leak:
>
> 1. It only occurs in development mode (production mode does not
> exhibit the leak)
> 2. It only occurs under FastCGI (running under WEBrick does not
> exhibit the leak)
> 3. The leak is due to an accumulation of Proc objects used with
> define_method, primarily in ActiveRecord (the define_method's in
> question are used to create the dynamic "has_one", "belongs_to",
> "has_many", and "habtm" accessors). The leak may also be due to
> define_method's used in ActionMailer, as well.

Someone on IRC made this claim a while ago so I looked into it. Doing
tens of thousands of define_methods certainly increased the memory
footprint, but explicit calls to GC.start every 100 calls or so kept
it to a bare minimum. I don't think it is a real leak, but I could be
wrong. I can talk to you about it in more depth if you grab me later
today or tomorrow.

Yukihiro Matsumoto

unread,
Oct 15, 2005, 4:16:12 PM10/15/05
to
Hi,

In message "Re: Help! define_method leaking procs..."


on Sun, 16 Oct 2005 05:01:58 +0900, Ryan Davis <ryand...@zenspider.com> writes:

|Someone on IRC made this claim a while ago so I looked into it. Doing
|tens of thousands of define_methods certainly increased the memory
|footprint, but explicit calls to GC.start every 100 calls or so kept
|it to a bare minimum. I don't think it is a real leak, but I could be
|wrong. I can talk to you about it in more depth if you grab me later
|today or tomorrow.

Since define_method creates reference to the block (a closure) which
holds reference to the variables in the external scope. So that if
it's not a real memory leak, isolating define_method with block by a
separate method may help, e.g. changing

define_method(:foo){...}

to

def def_something(name)
define_method(name){...}
end
...
def_something(:foo)

matz.


Eric Mahurin

unread,
Oct 15, 2005, 4:20:20 PM10/15/05
to


Take a look at this thread:

http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/3ad49507f2086e22/ac92a1373161035e?q=mahurin&rnum=4#ac92a1373161035e

I demonstrated some example code with a memory leak with
lambdas.


__________________________________
Yahoo! Music Unlimited
Access over 1 million songs. Try it free.
http://music.yahoo.com/unlimited/


ES

unread,
Oct 15, 2005, 4:50:44 PM10/15/05
to

I still do not think this is a memory leak. It is, perhaps, memory
that the implementer may not realize will be consumed but its
whereabouts are known and it is accessible.

E

Eric Mahurin

unread,
Oct 15, 2005, 6:17:49 PM10/15/05
to
--- ES <rub...@magical-cat.org> wrote:

So you definition of "memory leak" says that it is not a memory
leak if the unused memory can still be freed by the program.
With the example I gave, you could still free the memory using
the Proc#binding with eval to assign the unused variables to
nil. But, here is a slightly modified example where (using
#define_method) where you lose this access:

n=2**13;(1..n).each{|i|
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
VmSize: 172028 kB

As far as I know, you can't access any of thoses a's after you
get out of the each loop. I call that a memory leak by any
definition.

Here is the fixed version:

ruby -e '
n=2**13;(1..n).each{|i|
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GCstart;
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
VmSize: 11504 kB

Yohanes Santoso

unread,
Oct 15, 2005, 11:30:09 PM10/15/05
to
Eric Mahurin <eric_m...@yahoo.com> writes:

> n=2**13;(1..n).each{|i|
> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> VmSize: 172028 kB

> ruby -e '
> n=2**13;(1..n).each{|i|
> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GC.start;


> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

> VmSize: 11504 kB

Stop right there. I want to remind people that you can't use VmSize as
a leak indicator. In some OS, VmSize is an always increasing
number. Memory allocated in a process is not returned to the OS until
the process dies.

Here is a C program that free() every malloc(), yet still have
outrageous VmSize;

ysantoso@jenny:/tmp$ gcc -W -Wall ./leak.c -o leak && ./leak immed && ./leak not_immed
Freeing immediately
----BEFORE----
malloc count = 0
free count = 0
VmSize: 1572 kB
VmLck: 0 kB
VmRSS: 364 kB
VmData: 156 kB
VmStk: 88 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB
Executing leak test
unwinding
----AFTER-----
malloc count = 8193
free count = 8193
VmSize: 1572 kB
VmLck: 0 kB
VmRSS: 440 kB
VmData: 156 kB
VmStk: 88 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB
Not freeing immediately
----BEFORE----
malloc count = 0
free count = 0
VmSize: 1568 kB
VmLck: 0 kB
VmRSS: 364 kB
VmData: 156 kB
VmStk: 84 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB
Executing leak test
unwinding
freeing 8192 elements
----AFTER-----
malloc count = 8193
free count = 8192
VmSize: 206360 kB
VmLck: 0 kB
VmRSS: 205280 kB
VmData: 204948 kB
VmStk: 84 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 216 kB
ysantoso@jenny:/tmp$ cat ./leak.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int malloc_count = 0;
int free_count = 0;

void *
counting_malloc(size_t size)
{
malloc_count++;
return malloc(size);
}

void
counting_free(void *ptr)
{
free_count++;
if (ptr == NULL) {
fprintf(stderr, "Warning: free-ing NULL pointer\n");
}
free(ptr);
}

void
display_vminfo()
{
FILE *status = fopen("/proc/self/status", "r");
char line[132];
while (fgets(line, 132, status)) {
if (strstr(line, "Vm") == line) {
printf(line);
}
}
fclose(status);
}

/* allocs about 200M of mem. if free_immed_p, then memory allocated is free()ed immediately */
void
leak_test(int free_immed_p)
{
int i, j;
char **array = NULL;
int max_array_idx = -1;
int max_array_size = 8192;
int element_size = 25*1024;
array = counting_malloc(max_array_size * sizeof(char*));
if (!array) {
fprintf(stderr, "Unable to malloc\n");
goto die;
}
for (i=0; i < max_array_size; i++) {
char *element = counting_malloc(element_size);
if (!element) {
fprintf(stderr, "Unable to malloc\n");
goto die;
}
/* walk through the allocated mem to negate any lazy allocation schema */
for (j=0; j < element_size; j++) {
element[j] = '\0';
}
array[i] = element;
max_array_idx = i;
if (free_immed_p) {
counting_free(element);
}
}
die:
fprintf(stderr, "unwinding\n");
if (array) {
if (!free_immed_p) {
fprintf(stderr, "freeing %d elements\n", max_array_idx + 1);
for (i=0; i < max_array_idx; i++) {
counting_free(array[i]);
}
}
counting_free(array);
}
}

void
usage()
{
fprintf(stderr, "Don't understand argument. immed or not_immed\n");
}

int
main(int argc, char **argv)
{
int free_immed_p;
if (argc != 2) {
usage();
return 1;
}
if (strcmp("immed", argv[1]) == 0) {
printf("Freeing immediately\n");
free_immed_p = 1;
} else if (strcmp("not_immed", argv[1]) == 0) {
printf("Not freeing immediately\n");
free_immed_p = 0;
} else {
usage();
return 1;
}
printf("----BEFORE----\n");
printf("malloc count = %d\n", malloc_count);
printf("free count = %d\n", free_count);
display_vminfo();
printf("Executing leak test\n");
leak_test(free_immed_p);
printf("----AFTER-----\n");
printf("malloc count = %d\n", malloc_count);
printf("free count = %d\n", free_count);
display_vminfo();
return 0;
}

> Here is the fixed version:

You can't say this if you only have VmSize.


YS.


Yohanes Santoso

unread,
Oct 15, 2005, 11:49:21 PM10/15/05
to
Yohanes Santoso <ysantoso...@dessyku.is-a-geek.org> writes:

> Eric Mahurin <eric_m...@yahoo.com> writes:
>
>> n=2**13;(1..n).each{|i|
>> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
>> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
>> VmSize: 172028 kB
>
>> ruby -e '
>> n=2**13;(1..n).each{|i|
>> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GC.start;
>> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
>> VmSize: 11504 kB
>
> Stop right there. I want to remind people that you can't use VmSize as
> a leak indicator. In some OS, VmSize is an always increasing
> number. Memory allocated in a process is not returned to the OS until
> the process dies.
>
> Here is a C program that free() every malloc(), yet still have
> outrageous VmSize;

Sorry, I posted the wrong version. Here is the correct one:

ysantoso@jenny:/tmp$ gcc -W -Wall ./leak.c -o leak && ./leak immed && ./leak not_immed
Freeing immediately
----BEFORE----
malloc count = 0
free count = 0

VmSize: 1568 kB
VmLck: 0 kB

VmRSS: 360 kB


VmData: 156 kB
VmStk: 84 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB
Executing leak test

Allocating hole


unwinding
----AFTER-----
malloc count = 8193
free count = 8193

VmSize: 1568 kB
VmLck: 0 kB

VmRSS: 440 kB
VmData: 156 kB

VmStk: 84 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB

Freeing hole


Not freeing immediately
----BEFORE----
malloc count = 0
free count = 0
VmSize: 1568 kB
VmLck: 0 kB
VmRSS: 364 kB
VmData: 156 kB
VmStk: 84 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB
Executing leak test

Allocating hole


unwinding
freeing 8192 elements
----AFTER-----
malloc count = 8193

free count = 8193


VmSize: 206360 kB
VmLck: 0 kB

VmRSS: 205284 kB


VmData: 204948 kB
VmStk: 84 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 216 kB

Freeing hole


ysantoso@jenny:/tmp$ cat ./leak.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int malloc_count = 0;
int free_count = 0;

char *hole;

if (!hole) {
fprintf(stderr, "Allocating hole\n");
hole = malloc(1);
if (!hole) {
fprintf(stderr, "Unable to make hole\n");
goto die;
}
hole[0] = '\0';


}
die:
fprintf(stderr, "unwinding\n");
if (array) {
if (!free_immed_p) {
fprintf(stderr, "freeing %d elements\n", max_array_idx + 1);

for (i=0; i < max_array_idx+1; i++) {
counting_free(array[i]);
}
}
counting_free(array);
}
}

void
usage()
{
fprintf(stderr, "Don't understand argument. immed or not_immed\n");
}

int
main(int argc, char **argv)
{
int free_immed_p;

hole = NULL;


if (argc != 2) {
usage();

goto die;


}
if (strcmp("immed", argv[1]) == 0) {
printf("Freeing immediately\n");
free_immed_p = 1;
} else if (strcmp("not_immed", argv[1]) == 0) {
printf("Not freeing immediately\n");
free_immed_p = 0;
} else {
usage();

goto die;


}
printf("----BEFORE----\n");
printf("malloc count = %d\n", malloc_count);
printf("free count = %d\n", free_count);
display_vminfo();
printf("Executing leak test\n");
leak_test(free_immed_p);
printf("----AFTER-----\n");
printf("malloc count = %d\n", malloc_count);
printf("free count = %d\n", free_count);
display_vminfo();

die:
if (hole) {
fprintf(stderr, "Freeing hole\n");
free(hole);
}
return 0;
}
ysantoso@jenny:/tmp$


Wilson Bilkovich

unread,
Oct 15, 2005, 11:51:09 PM10/15/05
to
Has anyone tried throwing DTrace at this problem? I don't have any
Solaris access here at RubyConf, and I don't use FCGI, or I'd give it
a go.

Eric Mahurin

unread,
Oct 16, 2005, 12:21:19 AM10/16/05
to
--- Yohanes Santoso <ysantoso...@dessyku.is-a-geek.org>
wrote:

> Eric Mahurin <eric_m...@yahoo.com> writes:
>
> > n=2**13;(1..n).each{|i|
> >
>
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> > VmSize: 172028 kB
>
> > ruby -e '
> > n=2**13;(1..n).each{|i|
> >
>

a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GCstart;


> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> > VmSize: 11504 kB
>
> Stop right there. I want to remind people that you can't use
> VmSize as
> a leak indicator. In some OS, VmSize is an always increasing
> number. Memory allocated in a process is not returned to the
> OS until
> the process dies.

OK. Pick another way to measure memory. top shows the same
memory as above for me. Or make n=2**15. This brings my
machine (768MB) to its knees:

ruby -e 'n=2**15;(1..n).each{|i|


a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

I'd estimate it would use about 3GB. And if you put an a=nil
after the define_method, you get this:

ruby -e 'n=2**15;(1..n).each{|i|
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GCstart;


IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

VmSize: 31032 kB



__________________________________
Yahoo! Mail - PC Magazine Editors' Choice 2005
http://mail.yahoo.com


Yohanes Santoso

unread,
Oct 16, 2005, 12:51:34 AM10/16/05
to
Eric Mahurin <eric_m...@yahoo.com> writes:

> --- Yohanes Santoso <ysantoso...@dessyku.is-a-geek.org>
> wrote:
>
>> Eric Mahurin <eric_m...@yahoo.com> writes:
>>
>> > n=2**13;(1..n).each{|i|
>> >
>>
> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
>> >
>>
> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
>> > VmSize: 172028 kB
>>
>> > ruby -e '
>> > n=2**13;(1..n).each{|i|
>> >
>>

> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GC.start;


>> >
>>
> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

>> > VmSize: 11504 kB
>>
>> Stop right there. I want to remind people that you can't use
>> VmSize as
>> a leak indicator. In some OS, VmSize is an always increasing
>> number. Memory allocated in a process is not returned to the
>> OS until
>> the process dies.
>
> OK. Pick another way to measure memory. top shows the same
> memory as above for me. Or make n=2**15. This brings my
> machine (768MB) to its knees:
>
> ruby -e 'n=2**15;(1..n).each{|i|
> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
>
> I'd estimate it would use about 3GB. And if you put an a=nil
> after the define_method, you get this:
>
> ruby -e 'n=2**15;(1..n).each{|i|

> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GC.start;


> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

> VmSize: 31032 kB


That is still not a valid way to detect leak as that still depends on
VmSize value. Ruby could be freeing every allocation and the Vmsize
would still grow.

I posted two versions because the first version was
1. has an off-by-1 error,
2. does not show that the order of free() matters.

Try this diff where the hole is freed in different order. There is no
memory leak this time. The same number of allocations and frees, but
it has no memory leak!

Cheers,
YS.


--- leak.c 2005-10-16 00:18:34.000000000 -0400
+++ noleak.c 2005-10-16 00:40:36.000000000 -0400
@@ -74,6 +74,11 @@


goto die;
}
hole[0] = '\0';

+ if (hole) {
+ fprintf(stderr, "Freeing hole\n");
+ free(hole);
+ hole=NULL;
+ }


}
die:
fprintf(stderr, "unwinding\n");

Freeing immediately
----BEFORE----
malloc count = 0
free count = 0
VmSize: 1572 kB
VmLck: 0 kB
VmRSS: 364 kB
VmData: 156 kB
VmStk: 88 kB
VmExe: 4 kB
VmLib: 1280 kB
VmPTE: 16 kB
Executing leak test

Allocating hole
Freeing hole

Allocating hole
Freeing hole


unwinding
freeing 8192 elements
----AFTER-----
malloc count = 8193

free count = 8193
VmSize: 1572 kB
VmLck: 0 kB

VmRSS: 512 kB

Eric Mahurin

unread,
Oct 16, 2005, 1:27:57 AM10/16/05
to
--- Yohanes Santoso <ysantoso...@dessyku.is-a-geek.org>
wrote:

> Eric Mahurin <eric_m...@yahoo.com> writes:
>
> > --- Yohanes Santoso
> <ysantoso...@dessyku.is-a-geek.org>
> > wrote:
> >
> >> Eric Mahurin <eric_m...@yahoo.com> writes:
> >>
> >> > n=2**13;(1..n).each{|i|
> >> >
> >>
> >
>
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> >> >
> >>
> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> >> > VmSize: 172028 kB
> >>
> >> > ruby -e '
> >> > n=2**13;(1..n).each{|i|
> >> >
> >>
> >
>

a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GCstart;


> >> >
> >>
> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> >> > VmSize: 11504 kB
> >>
> >> Stop right there. I want to remind people that you can't
> use
> >> VmSize as
> >> a leak indicator. In some OS, VmSize is an always
> increasing
> >> number. Memory allocated in a process is not returned to
> the
> >> OS until
> >> the process dies.
> >
> > OK. Pick another way to measure memory. top shows the
> same
> > memory as above for me. Or make n=2**15. This brings my
> > machine (768MB) to its knees:
> >
> > ruby -e 'n=2**15;(1..n).each{|i|
> >
>
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> >
> > I'd estimate it would use about 3GB. And if you put an
> a=nil
> > after the define_method, you get this:
> >
> > ruby -e 'n=2**15;(1..n).each{|i|
> >
>

a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GCstart;


> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> > VmSize: 31032 kB
>
>
> That is still not a valid way to detect leak as that still
> depends on
> VmSize value. Ruby could be freeing every allocation and the
> Vmsize
> would still grow.

But it is not. Have you tried this example? On my 768MB
machine, top shows it filling my physical memory and then the
the CPU drops to about 3% because it starts swapping and my
machine becomes very unresponsive. I'd say that's a good
indication that top is reporting the right thing.

You could consider this case a bug in ruby or a bug in the
simple test case above. It doesn't matter where the fault
lies, it still shows a memory leak. The point is you better be
careful with closures (block/lambda) because they carry all the
variables in scope in Proc#binding in the current
implementation. Personally, I would like to see something done
about this in ruby, but at minimum people need to be aware of
this memory issue (as ruby stands now).

Ara.T.Howard

unread,
Oct 16, 2005, 2:21:10 AM10/16/05
to
On Sun, 16 Oct 2005, Eric Mahurin wrote:

> But it is not. Have you tried this example? On my 768MB machine, top shows
> it filling my physical memory and then the the CPU drops to about 3% because
> it starts swapping and my machine becomes very unresponsive. I'd say that's
> a good indication that top is reporting the right thing.
>
> You could consider this case a bug in ruby or a bug in the simple test case
> above. It doesn't matter where the fault lies, it still shows a memory
> leak. The point is you better be careful with closures (block/lambda)
> because they carry all the variables in scope in Proc#binding in the current
> implementation. Personally, I would like to see something done about this
> in ruby, but at minimum people need to be aware of this memory issue (as
> ruby stands now).

this is quite suspect:

[ahoward@localhost ~]$ for i in $(seq 0 18);do
printf "$i => ";
ruby -e' class << self; (2**ARGV.shift.to_i).times{|n| s = '42' * n; define_method("f#{ n }"){ 42 }}; end; GC::start; p IO::read("/proc/#{ $$ }/status").grep(/VmSize/) ' $i;
done

0 => ["VmSize:\t 2840 kB\n"]
1 => ["VmSize:\t 2840 kB\n"]
2 => ["VmSize:\t 2840 kB\n"]
3 => ["VmSize:\t 2840 kB\n"]
4 => ["VmSize:\t 2844 kB\n"]
5 => ["VmSize:\t 2840 kB\n"]
6 => ["VmSize:\t 2840 kB\n"]
7 => ["VmSize:\t 2844 kB\n"]
8 => ["VmSize:\t 2976 kB\n"]
9 => ["VmSize:\t 3456 kB\n"]
10 => ["VmSize:\t 3720 kB\n"]
11 => ["VmSize:\t 4380 kB\n"]
12 => ["VmSize:\t 6072 kB\n"]
13 => ["VmSize:\t 9456 kB\n"]
14 => ["VmSize:\t 15996 kB\n"]
15 => ["VmSize:\t 28704 kB\n"]
16 => ["VmSize:\t 53352 kB\n"]
17 => ["VmSize:\t 101220 kB\n"]
18 => ["VmSize:\t 194404 kB\n"]


note the progression is linear until 13 (note irony) and then climbs
exponentially - doubling each time.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| anything that contradicts experience and logic should be abandoned.
| -- h.h. the 14th dalai lama
===============================================================================

Ara.T.Howard

unread,
Oct 16, 2005, 2:48:53 AM10/16/05
to

and even wrapping in a method doesn't seem to help

[ahoward@localhost ~]$ ruby a.rb 15
1 => VmSize: 2844 kB
2 => VmSize: 2848 kB
4 => VmSize: 2848 kB
8 => VmSize: 2848 kB
16 => VmSize: 2848 kB
32 => VmSize: 2848 kB
64 => VmSize: 2976 kB
128 => VmSize: 3108 kB
256 => VmSize: 3724 kB
512 => VmSize: 4648 kB
1024 => VmSize: 5572 kB
2048 => VmSize: 9904 kB
4096 => VmSize: 13104 kB
8192 => VmSize: 27016 kB
16384 => VmSize: 46676 kB


[ahoward@localhost ~]$ cat a.rb
class Object
def gen_method
klass = Class === self ? self : self.class
class << klass
define_method("f#{ rand(2 ** 42) }"){ 42 }
end
end
end

limit = Integer ARGV.shift

limit.times do |l|
n = 2 ** l

n.times{|i| s = '42' * i and gen_method}

GC::start

printf "% 8d => %s", n, IO::read("/proc/#{ $$ }/status").grep(/VmSize/)
end

still exponential after a threshold...

Yohanes Santoso

unread,
Oct 16, 2005, 3:02:26 AM10/16/05
to
Eric Mahurin <eric_m...@yahoo.com> writes:

>> That is still not a valid way to detect leak as that still
>> depends on
>> VmSize value. Ruby could be freeing every allocation and the
>> Vmsize
>> would still grow.
>
> But it is not.

Be careful, 1. there is no evidence yet that ruby is not freeing every
allocation, and 2. I am not saying there is no memory leak caused by
define_method.

What I have been saying is, you can't use VmSize as an indicator of
memory leak. I have shown examples where the order of free() can
affect VmSize.

Here is an alternative scenario that would have resulted in what you
are seeing: ruby could have free()-ed all its allocations properly
when you call GC.start, but may free() them in different order on each
test case which result in you seeing different VmSize values.

Probably that is not what happens; Probably there really is a genuine
memory leak; But you can't determine which scenario is happening from
VmSize value.

> Have you tried this example? On my 768MB machine, top shows it
> filling my physical memory and then the the CPU drops to about 3%
> because it starts swapping and my machine becomes very unresponsive.
> I'd say that's a good indication that top is reporting the right
> thing.

This only shows whether the free-ing is done immediately or not. Thus,
why my example program focuses in the immediateness of freeing.

> You could consider this case a bug in ruby or a bug in the
> simple test case above. It doesn't matter where the fault
> lies, it still shows a memory leak.

Memory leak is tricky to show since it requires one to prove that
there is an allocation that is not free-ed by the time the process
ends.

However, there are evidences (search the archive for GC problems) that
the GC is too conservative (read: lazy) and stupid in some
cases. Sometimes, you need to drop the clue book on its head, like
doing a=nil. Without the a=nil, the number of objects is not decreased
by much after GC.

> The point is you better be careful with closures (block/lambda)

Yes, be careful as the GC is a lazy bum.

In any case, here is another 'fixed' version.

ysantoso@jenny:/tmp$ ruby -e 'n=2**13;for i in (1..n) do
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i} end;GC.start;


IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

VmSize: 11268 kB

YS.


Yohanes Santoso

unread,
Oct 16, 2005, 3:19:09 AM10/16/05
to
"Ara.T.Howard" <Ara.T....@noaa.gov> writes:

> still exponential after a threshold...

I think that's because the symbol table grows exponentially.

YS.


Randy Kramer

unread,
Oct 16, 2005, 8:12:30 AM10/16/05
to
I'm really not trying to start a flamewar. Some of the statements made in
this thread seem completely contrary to what I thought I knew, and
counter-intuitive as well.

Am I completely mixed up?

On Sunday 16 October 2005 03:02 am, Yohanes Santoso wrote:
> Memory leak is tricky to show since it requires one to prove that
> there is an allocation that is not free-ed by the time the process
> ends.

I didn't/don't think so. Unused memory allocations that are not freed while
the process is running seem to be the very definition of a memory leak:

Result of Googling [define:"memory leak"]:
=
Definitions of "memory leak" on the Web:

Making malloc calls without the corresponding calls to free. The result is
that the amount of heap memory used continues to increase as the process
runs.
techpubs.sgi.com/library/tpl/cgi-bin/getdoc.cgi

The effect of a program that maintains references to objects that are no
longer required and therefore need to be reclaimed by garbage collection
routines.
publib.boulder.ibm.com/infocenter/adiehelp/topic/com.ibm.wsinted.glossary.doc/topics/glossary.html

A programming term describing the losing of memory. This happens when the
program allocates some memory but fails to return it to the system. Excessive
memory leaks can lead to program failure after a sufficiently long period of
time.
www.fieldpine.com/help/en/user/glossary/m.htm

Memory leaks are often thought of as failures to release unused memory by a
computer program. Strictly speaking, it is just unneccesary memory
consumption. A memory leak occurs when the program loses the ability to free
the memory. A memory leak diminishes the performance of the computer, as it
becomes unable to use all its available memory.
en.wikipedia.org/wiki/Memory_leak
=

I'd call it a memory leak even if the allocation is freed when the process
ends. Consider processes that (should) run continuously.

From an earlier post:

On Saturday 15 October 2005 11:30 pm, Yohanes Santoso wrote:
> Stop right there. I want to remind people that you can't use VmSize as
> a leak indicator.

That may be true (I really don't know)--but:
* if increasing VmSize corresponds to a slowdown in my machine, I'd call it
a pretty good "indicator"
* what can you use as a better indicator

> In some OS, VmSize is an always increasing
> number.

Out of curiosity (and the intent to try to avoid such), which OS? Or maybe I
better clarify your statement--are you saying that in some OS, VmSize does
not decrease even if a program is stopped?

> Memory allocated in a process is not returned to the OS until
> the process dies.

Again, seems like the very defintion of a memory leak.

It is true that if the memory allocated to a process is returned to the OS
when the process dies, that gives you a workaround for the memory leak, but
it is still a memory leak.

I can remember some programs that I ran in Windows that I had to occasionally
stop and restart for just this reason. I'm beginning to think that some
"well known" programs in Linux behave in a similar way. (I shouldn't really
name names yet, as I haven't quite proved it to my own satisfaction, but I do
occasionally close kmail and epihany and get back a lot of memory (and make
my system run a lot better.)

Aside: I did note your other comments from a later post:

> Be careful, 1. there is no evidence yet that ruby is not freeing every
> allocation, and 2. I am not saying there is no memory leak caused by
> define_method.

> What I have been saying is, you can't use VmSize as an indicator of
> memory leak. I have shown examples where the order of free() can
> affect VmSize.

regards,
Randy Kramer

Eric Mahurin

unread,
Oct 16, 2005, 9:40:40 AM10/16/05
to
--- Yohanes Santoso <ysantoso...@dessyku.is-a-geek.org>
wrote:

> Eric Mahurin <eric_m...@yahoo.com> writes:

Just because a process frees all its memory before it
terminates doesn't mean there isn't a leak. That doesn't help
too much if you run out of memory before the process is done.
I've seen 2 definitions: a) when a program isn't freeing memory
that isn't used anymore, b) when a program loses all references
to memory that isn't being freed. From the ruby script level
for this example, by both of these definitions it is a memory
leak. From ruby itself, ruby probably still has references to
the memory, so (b) may not call it a "memory leak". To me (a)
is the right definition because it describes symtoms not the
mechanism. If any program keeps growing in memory and it
shouldn't, I'd say it has a memory leak. It doesn't matter how
it comes about and whether or not the program could free the
memory if it was fixed, it is still "leaking" memory.

> However, there are evidences (search the archive for GC
> problems) that
> the GC is too conservative (read: lazy) and stupid in some
> cases. Sometimes, you need to drop the clue book on its head,
> like
> doing a=nil. Without the a=nil, the number of objects is not
> decreased
> by much after GC.
>
> > The point is you better be careful with closures
> (block/lambda)
>
> Yes, be careful as the GC is a lazy bum.

If you want to assign blame to ruby, the problem is not GC.
The problem is that closures hold strong references to all
variables in the scope whether or not they are used. One of my
solutions was to make references (in Proc#binding) to currently
unused variables (by the closure) "weak", so that they wouldn't
prevent GC.

> In any case, here is another 'fixed' version.
>
> ysantoso@jenny:/tmp$ ruby -e 'n=2**13;for i in (1..n) do
> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}
> end;GC.start;
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
>
> VmSize: 11268 kB

This is because the "for" loop (which uses #each) doesn't
create the same type of block/lambda that the normal block
(after a method call) does. In the above example, "a" is in
the outer scope. With using #each and a block directly, "a" is
a local to each interation that it is yielded - it is a
different variable on each iteration. The "for" loop you
showed actually doesn't even work the same because all of the
defined f* methods will use the same "i" because "i" is not
local for each iteration for "for" but it is for #each using a
normal block.

__________________________________________________
Do You Yahoo!?
Tired of spam? Yahoo! Mail has the best spam protection around
http://mail.yahoo.com


ts

unread,
Oct 16, 2005, 9:51:35 AM10/16/05
to
>>>>> "E" == Eric Mahurin <eric_m...@yahoo.com> writes:

E> to memory that isn't being freed. From the ruby script level
E> for this example, by both of these definitions it is a memory
E> leak. From ruby itself, ruby probably still has references to
E> the memory, so (b) may not call it a "memory leak". To me (a)
E> is the right definition because it describes symtoms not the
E> mechanism. If any program keeps growing in memory and it
E> shouldn't, I'd say it has a memory leak. It doesn't matter how
E> it comes about and whether or not the program could free the
E> memory if it was fixed, it is still "leaking" memory.

No please.

You are writing stupid program, don't expect that ruby correct what you
write. ruby is a programming language, first learn it before trying to
blame it.


Guy Decoux


Eric Mahurin

unread,
Oct 16, 2005, 10:05:59 AM10/16/05
to
--- "Ara.T.Howard" <Ara.T....@noaa.gov> wrote:

The other leaking examples were technically quadratic (O(n**2))
were they should have been O(n). The above one is also O(n).
Take on the "s = '42' * i and" and you'll see the same memory
usage. I don't see an issue in the above example. It's just
that the memory usage was so small for anything below n=1024,
that it probably didn't have to grow much beyond what ruby had
allocated to it when it started up.

Ara.T.Howard

unread,
Oct 16, 2005, 10:13:47 AM10/16/05
to
On Sun, 16 Oct 2005, Eric Mahurin wrote:

>> Memory leak is tricky to show since it requires one to prove that there is
>> an allocation that is not free-ed by the time the process ends.
>
> Just because a process frees all its memory before it > terminates doesn't
> mean there isn't a leak. That doesn't help too much if you run out of
> memory before the process is done. I've seen 2 definitions: a) when a
> program isn't freeing memory that isn't used anymore, b) when a program
> loses all references to memory that isn't being freed. From the ruby script
> level for this example, by both of these definitions it is a memory leak.
> From ruby itself, ruby probably still has references to the memory, so (b)
> may not call it a "memory leak". To me (a) is the right definition because
> it describes symtoms not the mechanism. If any program keeps growing in
> memory and it shouldn't, I'd say it has a memory leak. It doesn't matter
> how it comes about and whether or not the program could free the memory if
> it was fixed, it is still "leaking" memory.

by your definition the code i was working with last week is 'leaky': it
malloc's an 4GB grid (yes that's right) and does nearest neighbor filling on
it. if you think about it for a few moments you'll realize that only a small
window of pixels needs to be in memory at once and, after a pixel has been
written it will never be needed again, except very soon after as a neighbor
itself, thus it need not remain in memory. the code didn't run - it ran out of
memory. there is not leak, just too large a memory requirement. changing the
malloc/write/free to mmap/munmap fixed the problem - proving there was no leak
since allocating/deallocating a different way allowed the code to run and
recognizing that the code had not lost the ability (pointer) to free the
memory, it just hadn't yet.

they key to 'a' above is "isn't used anymore." all ruby knows is that your
proc __might__ need __anything__, for instance:

harp:~ > cat a.rb
def scope
a, b = 4, 2
lambda{|s| eval "p #{ s }" }
end

scope[ ARGV.shift ]

harp:~ > ruby a.rb a
4

harp:~ > ruby a.rb b
2

so it must, according the present promised behaviour of lambda, preverse
everything. so it seems that it cannot be a leak according to 'a' because it's
t.b.d if the memory will be used or not and it cannot be a leak according to
'b' because references are clearly not lost. an interpreter cannot manage
memory as efficiently as a very bright programmer - it can do it better than
most though.

fork/drb/ and co-processes are good tools for cases like this. it's easy to
setup a memory sandbox in ruby that you can pass and return objects from and
which is guarunteed to free all memory (on exit).

in any case, it seems the issue is that people may not always like what lambda
does, but it seems like it's doing it properly.

> If you want to assign blame to ruby, the problem is not GC.
> The problem is that closures hold strong references to all
> variables in the scope whether or not they are used. One of my
> solutions was to make references (in Proc#binding) to currently
> unused variables (by the closure) "weak", so that they wouldn't
> prevent GC.

agreed - but no 'leak'.

cheers.

Ara.T.Howard

unread,
Oct 16, 2005, 10:15:37 AM10/16/05
to
On Sun, 16 Oct 2005, Eric Mahurin wrote:

>> still exponential after a threshold...
>
> The other leaking examples were technically quadratic (O(n**2)) were they
> should have been O(n). The above one is also O(n). Take on the "s = '42' *
> i and" and you'll see the same memory usage. I don't see an issue in the
> above example. It's just that the memory usage was so small for anything
> below n=1024, that it probably didn't have to grow much beyond what ruby had
> allocated to it when it started up.

you are correct : never send email at 1am ;-)

so, no leak from define_method then.

Eric Mahurin

unread,
Oct 16, 2005, 10:19:54 AM10/16/05
to
--- ts <dec...@moulon.inra.fr> wrote:

The above paragraph doesn't even blame ruby. This thread
started with a real ruby program "leaking" memory. I gave a
possible reason as that closures hold strong references to all
variables in there scope (with a leaky example). Matz also
responded to this thread giving the exact same possibility. He
has also discussed addressing this issue in another thread
(where he said eval could be a keyword). Does he need to go
learn the language better too?

ts

unread,
Oct 16, 2005, 10:23:37 AM10/16/05
to
>>>>> "E" == Eric Mahurin <eric_m...@yahoo.com> writes:

E> The above paragraph doesn't even blame ruby. This thread
E> started with a real ruby program "leaking" memory. I gave a
E> possible reason as that closures hold strong references to all
E> variables in there scope (with a leaky example).

This is really the first time that you use a language which has closure ?


Guy Decoux


Eric Mahurin

unread,
Oct 16, 2005, 10:53:51 AM10/16/05
to
--- ts <dec...@moulon.inra.fr> wrote:

Why do you feel the need to get personal? And I noticed you
deleted the sentences where Matz discussed the same issue.

To answer you question though - No. I've used perl for over 10
years before coming to ruby. I think perl is where closures
started. And here is the same type of example with perl:

perl -e '@f=();$n=2**$ARGV[0];for my $i (0..$n)
{my($a)=[0..$i];push(@f,sub{$i*$i})};system("cat
/proc/$$/status|grep VmSize")' 14
VmSize: 12812 kB

The memory usage was O(n).

Compare this to the case where it should blow up because
closure references the large array:

perl -e '@f=();$n=2**$ARGV[0];for my $i (0..$n)
{my($a)=[0..$i];push(@f,sub{$a;$i*$i})};system("cat
/proc/$$/status|grep VmSize")' 14
Killed

So perl only keeps variables that it uses with the closure.
Ruby keeps everything because of eval and Proc#binding. But, I
still think this needs to be addressed.



__________________________________
Start your day with Yahoo! - Make it your home page!
http://www.yahoo.com/r/hs


ts

unread,
Oct 16, 2005, 11:02:10 AM10/16/05
to
>>>>> "E" == Eric Mahurin <eric_m...@yahoo.com> writes:

E> To answer you question though - No. I've used perl for over 10
E> years before coming to ruby. I think perl is where closures
E> started. And here is the same type of example with perl:

Bad reference, change your reference.


Guy Decoux


Ara.T.Howard

unread,
Oct 16, 2005, 12:23:14 PM10/16/05
to

can you show us what you mean guy?

ES

unread,
Oct 16, 2005, 12:44:58 PM10/16/05
to

Yes. As far as I knew, this was the only sensible definition.
If there is a problem, it is not a memory leak (as most or all
C programmers understand the term anyway); changing the term might
yield a more successful debate.

> With the example I gave, you could still free the memory using
> the Proc#binding with eval to assign the unused variables to
> nil. But, here is a slightly modified example where (using
> #define_method) where you lose this access:
>
> n=2**13;(1..n).each{|i|
> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> VmSize: 172028 kB
>
> As far as I know, you can't access any of thoses a's after you
> get out of the each loop. I call that a memory leak by any
> definition.
>
> Here is the fixed version:
>
> ruby -e '
> n=2**13;(1..n).each{|i|

> a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GC.start;


> IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'

> VmSize: 11504 kB

E

Eric Mahurin

unread,
Oct 16, 2005, 1:33:19 PM10/16/05
to
--- ES <rub...@magical-cat.org> wrote:
> > So your definition of "memory leak" says that it is not a

> memory
> > leak if the unused memory can still be freed by the
> program.
>
> Yes. As far as I knew, this was the only sensible definition.
> If there is a problem, it is not a memory leak (as most or
> all
> C programmers understand the term anyway); changing the term
> might
> yield a more successful debate.

Try a google on "memory leak". The first several defintions
you'll find are more general and not limited to languages that
don't have automatic GC. Just because you have a perfect
language with perfect GC doesn't mean you can't write a program
that "leaks" memory - over time uses more and more memory when
it doesn't need it. I think the language/GC/libs should try to
plug as many holes as possible, but you still won't be able to
prevent all memory leak bugs of programs written in the
language.

> > With the example I gave, you could still free the memory
> using
> > the Proc#binding with eval to assign the unused variables
> to
> > nil. But, here is a slightly modified example where (using
> > #define_method) where you lose this access:
> >
> > n=2**13;(1..n).each{|i|
> >
>
a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i}};GC.start;
> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> > VmSize: 172028 kB
> >
> > As far as I know, you can't access any of thoses a's after
> you
> > get out of the each loop. I call that a memory leak by any
> > definition.
> >
> > Here is the fixed version:
> >
> > ruby -e '
> > n=2**13;(1..n).each{|i|
> >
>

a=(1..i).to_a;self.class.send(:define_method,:"f#{i}"){i*i};a=nil};GCstart;


> >
>
IO.readlines("/proc/#{Process.pid}/status").grep(/VmSize/).display'
> > VmSize: 11504 kB

Yohanes Santoso

unread,
Oct 16, 2005, 1:49:16 PM10/16/05
to
Randy Kramer <rhkr...@gmail.com> writes:

> I'm really not trying to start a flamewar. Some of the statements made in
> this thread seem completely contrary to what I thought I knew, and
> counter-intuitive as well.

Based on what you wrote below, the statements I made are in
agreement with what you thought you knew for the most part.

> On Sunday 16 October 2005 03:02 am, Yohanes Santoso wrote:
>> Memory leak is tricky to show since it requires one to prove that
>> there is an allocation that is not free-ed by the time the process
>> ends.
>
> I didn't/don't think so. Unused memory allocations that are not freed while
> the process is running seem to be the very definition of a memory
> leak:

I can go with this definition. The difference between this and mine is
the word 'unused'. In a language with GC, 'unused' is an expensive
(difficult too) proposition to automatically detect. If the GC is
aggressive, the number of unused allocations has a higher probability
of being smaller than a conservative GC, and Ruby has a conservative
GC. There is this trade-off of being agressive & more expensive
vs. conservative & cheaper.

> Result of Googling [define:"memory leak"]:

I agree with all these statements, and they do not contradict my
statement either.

In addition, I'd like to emphasis this:

> Memory leaks are often thought of as failures to release unused memory by a
> computer program. Strictly speaking, it is just unneccesary memory
> consumption.

unused allocation is just an alias for unnecessary memory consumption.

> A memory leak occurs when the program loses the ability to free
> the memory.

> en.wikipedia.org/wiki/Memory_leak

No evidence yet that the GC loses the ability to free the
memory. Proofing this amounts to proofing that an allocation is not
freed by the time the process ends. Which is exactly my statement.

> I'd call it a memory leak even if the allocation is freed when the process
> ends. Consider processes that (should) run continuously.

Then this really falls under the category 'unnecessary memory
consumption' as per the reference you gave.


> That may be true (I really don't know)--but:
> * if increasing VmSize corresponds to a slowdown in my machine, I'd call it
> a pretty good "indicator"

Try tracing through leak not_immed. You will see this peculiar
variable called 'hole'. hole holds a pointer to the last allocation
performed. It's there to show that allocated memory is not necessarily
returned to OS.

By the time it gets to the 'unwinding' part, there are exactly 8194
allocations (8193 for array&elements, 1 for hole). By the time the
execution returns back to main and displays the vminfo, there are
exactly 8193 free(). There is still 1 allocation that is not
freed. Yet, the VmSize shows that the other 8193 free() does not
result in the memory being returned to the OS.

What I was saying to Eric Mahurin was, the ruby gc, even being
conservative as it is, could have free()ed all the unnecessary
allocations yet, there could still be one allocation that would result
in every freed allocations in not being returned to the OS. In this
case, a program really cannot do anything.

So a large VmSize value could have meant anything: it could have meant
that the gc is too conservative, it could have meant the gc is buggy,
it could have meant the gc has free()ed all allocations saves one, it
could have meant ..., etc.

> * what can you use as a better indicator

object count would be a better indicator.

def count_objects
object_count=0
ObjectSpace.each_object{ object_count += 1}
object_count
end

Had Eric used this, I would not have been involved at all in this
thread.

>> In some OS, VmSize is an always increasing
>> number.
> Out of curiosity (and the intent to try to avoid such), which OS?

My first RL experience with this is in 1997 while working with a
HP-UX. Memory allocated was never returned to the OS. If you were to
modify leak so that it free() hole immediately and run it on that OS,
the VmSize would not decrease, although the VmRes would (see below).

> better clarify your statement--are you saying that in some OS, VmSize does
> not decrease even if a program is stopped?

VmSize depends on the existence of the process, as such, if the
process dies, VmSize no longer exists too. But I'm guessing that your
real question is whether the OS will reclaim the memory when a process
dies. In all *NIX OS, this is a true statement. Some versions of win95
(win3.11 too?) do not. Make a program that allocates some memory, kill
the program, run it again, kill it again, until you see allocation
failure.

>> Memory allocated in a process is not returned to the OS until
>> the process dies.
>
> Again, seems like the very defintion of a memory leak.

'unnecessary memory consumption' :)



> It is true that if the memory allocated to a process is returned to the OS
> when the process dies, that gives you a workaround for the memory leak, but
> it is still a memory leak.

Yeah, this depends on your definition of memory leak. It is an example
where terms have taken new meanings (incorporating the concept of
'unnecessary memory consumption'). Another example would be the word
'scalable' which today would be taken as 'high-performant' (thanks
zedas) by many people, or a more extreme example would be 'hacker'
which has became to mean the opposite, 'the bad guy'.

> occasionally close kmail and epihany and get back a lot of memory (and make
> my system run a lot better.)

In *NIX OS, what you should really be watching for is not VmSize,
rather VmRes[ident] which shows you how much RAM your program is
really taking. The diff of VmSize-VmRes would be in swap where it
could just stay there until the process ends.

This certainly is true in long-running processes. For example, on my
desktop system which has been running for 3 months (and some
applications have been running that long too: emacs, kde, konq), the
total VmSize of all processes is around 3GB, way more than the amount
of RAM I have (around 700MB), yet those apps still respond crisply,
and the total VmRes of all process is still under 400M.

> regards,
> Randy Kramer

Regards,
YS.


Ryan Davis

unread,
Oct 16, 2005, 6:38:34 PM10/16/05
to

On Oct 16, 2005, at 7:53 AM, Eric Mahurin wrote:

> [...] I think perl is where closures started. [...]

To quote http://www.paulgraham.com/avg.html :

"Lexical closures, introduced by Lisp in the early 1970s, are now,
just barely, on the radar screen."

Lyndon Samson

unread,
Oct 16, 2005, 8:57:27 PM10/16/05
to
Have you thought about a ruby leak detector?

You could monitor object allocation with define_finalizer and a wrapped new

Randy Kramer

unread,
Oct 17, 2005, 8:26:43 AM10/17/05
to
On Sunday 16 October 2005 01:49 pm, Yohanes Santoso wrote:
> Randy Kramer <rhkr...@gmail.com> writes:
> > I'm really not trying to start a flamewar. Some of the statements made
> > in this thread seem completely contrary to what I thought I knew, and
> > counter-intuitive as well.
>
> Based on what you wrote below, the statements I made are in
> agreement with what you thought you knew for the most part.

---<good stuff snipped>---

Thanks for the reply--it will probably take me a little while to digest this.
After I do, if I have any questions/comments, I'll write again.

regards,
Randy Kramer


Jamis Buck

unread,
Oct 17, 2005, 12:58:07 PM10/17/05
to
On Oct 16, 2005, at 6:57 PM, Lyndon Samson wrote:

> Have you thought about a ruby leak detector?
>
> You could monitor object allocation with define_finalizer and a

> wrapped new.
> Sort of like replacing new/delete in C++

We have identified and fixed the leak, and all is well now. Thanks,
everyone, for your suggestions.

If you're curious, the problem was fixed by calling undef_method on
all the dynamically added methods before doing remove_const on the
class. We also went through each class and removed all
instance_variables from the class. Doing this seems to have allowed
the Proc objects to be garbage collected.

- Jamis

Eric Mahurin

unread,
Oct 17, 2005, 1:07:51 PM10/17/05
to

Would you mind showing us a snippet of code that demonstrates
the problem? Maybe there is some circular references?

Jamis Buck

unread,
Oct 17, 2005, 1:33:27 PM10/17/05
to
On Oct 17, 2005, at 11:07 AM, Eric Mahurin wrote:

> --- Jamis Buck <ja...@37signals.com> wrote:
>
>> On Oct 16, 2005, at 6:57 PM, Lyndon Samson wrote:
>> We have identified and fixed the leak, and all is well now.
>> Thanks,
>> everyone, for your suggestions.
>>
>> If you're curious, the problem was fixed by calling
>> undef_method on
>> all the dynamically added methods before doing remove_const
>> on the
>> class. We also went through each class and removed all
>> instance_variables from the class. Doing this seems to have
>> allowed
>> the Proc objects to be garbage collected.
>>
>
> Would you mind showing us a snippet of code that demonstrates
> the problem? Maybe there is some circular references?

Well... here's a small script that duplicates the problem, but it
requires that it be run from the root directory of a Rails
application that has a model class called Dummy. And Dummy must have
an association to some other model class.

require './config/environment'

module ObjectSpace
def self.count(mod=Object)
count = 0
ObjectSpace.each_object(mod) { count += 1 }
count
end
end

20.times do
GC.start

Dummy.find(1)
Dependencies.clear
ActiveRecord::Base.reset_subclasses
Dependencies.remove_subclasses_for(ActiveRecord::Base)

procs = ObjectSpace.count(Proc)

puts "procs: #{procs}"
end

The set it up for this, you could do:

* gem install rails
* rails memleak
* cd memleak
* script/generate model Dummy
* script/generate model Thing
* edit Dummy so that it has_one :thing
* edit config/database.yml to point to a database
* add a dummies table
* insert a record into the dummies table
* run the script given above

Running against Rails 0.13.1, you should see the number of procs grow
with each request. Running against the beta gems, you'll see the
number remain constant.

- Jamis


0 new messages