juddhuck has asked for the wisdom of the Perl Monks concerning the following question:

Hi All!

I ran into a memory leak in a system I'm working on, and I narrowed it down to a problem when using "goto &subroutine" for tail recursion. Here is a simple script that recreates the problem:

#!/usr/bin/perl use strict; use warnings; bar(); sub bar { if ( foo() ) { goto &bar; } return; } my $i = 0; sub foo { return $i++ < 1_000_000_000; }
This script quickly leaks memory, but if I just move the contents of foo() into the if conditional, it doesn't leak at all. Also, if I move the call to foo() before the conditional it doesn't leak.

The stack and all lexical variables should be deallocated when goto is called, right? I think something strange is happening with the memory used for the return value of foo() when it is called in the conditional block. Any ideas?

Thanks,
Justin

Replies are listed 'Best First'.
Re: memory leak when using tail recursion?
by Limbic~Region (Chancellor) on Feb 13, 2007 at 23:58 UTC
    juddhuck,
    I don't know much about the optree, but perl -MO=Concise may be of help in comparing the versions that leak against the ones that don't. Incidently, the following doesn't but is functionally equivalent:
    #!/usr/bin/perl use strict; use warnings; my $i = 0; bar(); sub bar { goto &bar if foo(); return; } sub foo {return $i++ < 1_000_000_000;}

    Cheers - L~R

Re: memory leak when using tail recursion?
by ikegami (Patriarch) on Feb 14, 2007 at 00:03 UTC

    I ran into a memory leak in a system I'm working on

    I see no leekage. Memory usage is more or less constant.

    #!/usr/bin/perl use strict; use warnings; sub bar { if ( foo() ) { goto &bar; } return; } my $i; sub foo { return $i++ < 500_000; } $i=0; bar(); print("Press Enter to continue"); <STDIN>; # 32,824K $i=0; bar(); print("Press Enter to continue"); <STDIN>; # 32,872K $i=0; bar(); print("Press Enter to continue"); <STDIN>; # 32,860K $i=0; bar(); print("Press Enter to continue"); <STDIN>; # 32,856K

    The stack and all lexical variables should be deallocated when goto is called, right?

    Should? Dunno. Does? No. ( That's why the memory usage increases for each recursive call to bar. )

      ikegami,
      What version of perl? I ask because I see the memory leak locally with perl -v
      This is perl, v5.8.8 built for MSWin32-x86-multi-thread (with 25 registered patches, see perl -V for more detail) Copyright 1987-2006, Larry Wall Binary build 817 [257965] provided by ActiveState http://www.ActiveSta +te.com Built Mar 20 2006 17:54:25

      Cheers - L~R

        The exact same build.

        The memory usage does go up for each recursive call to bar (which is why I had to lower the max value for $i), but it does get reclaimed.

      I should clarify. The memory does eventually get reclaimed, so this isn't a true memory leak. The memory is just tied up until the final recursive call returns. If the value returned by foo() is large, or if there a large number of recursive calls, then this can cause the system to run out of memory.

      I originally ran into this problem when using Exception::Class to handle errors. I had an exception that caused a tail-recursive call to the current function, and this exception was happening repeatedly because there were a lot of invalid records in a row. Exception::Class objects are pretty large, so the system eventually ran out of memory. The code looked something like this:

      sub get { my ($self) = @_; my ($record, $e); eval { $record = $self->create_record(); }; if ( $e = Exception::Record::Corrupt->caught() ) { # We got a recoverable error, so skip this record. goto &get; } elsif ( $e = Exception::Class->caught() ) { # Rethrow any generic exception caught and abort. ref $e ? $e->rethrow : die $e; } return $record; }
      Anyway, there are plenty of ways to fix this problem. The easiest is to not use tail-recursion, but it *should* work with tail recursion. The odd thing to me is that this problem only occurs when the function call is in the if-conditional.
Re: memory leak when using tail recursion?
by Cabrion (Friar) on Feb 14, 2007 at 00:00 UTC

    See perldoc -f goto on your system.

    Goto with a subroutine reference isn't a goto in the traditional sense. You need to use a label. They way you are using it is identical to calling bar().

      Cabrion,
      Perhaps you aren't familiar with what tail recursion is. You must use this specific form of goto for tail recursion which is the intent of the post. Unfortunately, it is leaking in a very specific case. I don't know why but you may want to check out Pure Perl tail call optimization as your post isn't really helpful.

      Cheers - L~R

        Sorry, and I'm not trying to start an argument by way of reply. In other languages goto is a jump. In perl I recalled that goto &foo wasn't "traditional" goto, I assumed (incorrectly) that calling it this way instead of implementing with a line label was the source of the leak. The docs say that localized variables are cleared, but says nothing about clearing anything else. Just some weird magic used for AUTOLOAD.

        In the end, your are correct that I'm not even a novice in tail-recursion, and probably should have kept my mouth shut, or at least qualifed my post.