Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to recursively call one anonymous sub from another?

Tags:

perl

mason

The reason I want to use anonymous subs instead of named ones is because I want to define these subs inside Mason subcomponents(http://www.masonbook.com/book/chapter-2.mhtml#TOC-ANCHOR-7), which don't behave well with named subs.

E.g. if I write code in that way:

my ($first, $second);
$first = sub {
    my $val = shift;
    print "val: $val";
    $second->($val);
};
$second = sub {
    my $val = shift;
    if (0 < $val) {
        $val = $val - 1;
        $first->($val);
    }
};
$first->(10);

Are there any hidden gotchas(e.g. memory leaks, etc.) in this approach?

As explained by @Schwern, memory for these subs won't be released by Perl, as there's a circular reference between them.

But more specifically, will the memory allocation grow linearly, as $val is increased, or it doesn't depend on invocation stack depth? Because I can put these subs in mason <%once> blocks, and in that case these subs will be initialized only once.

like image 972
Yakov Avatar asked Sep 29 '15 18:09

Yakov


1 Answers

The only one I can think of is that the subroutines will never be deallocated, even if $first and $second go out of scope. $first's code refers to $second, $second's code refers to $first. This is a circular data structure, and Perl's memory allocation cannot deallocate that.

$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub {};  $second = sub {} } say "Done"; sleep 1000'

$ perl -wlE 'for (1..10_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() } } say "Done"; sleep 1000'

The first Perl process uses 1912K after the loop, the second uses 10320K. The first will not grow no matter how many CVs are created, the second will.

To get around this, you have to break the circle by undefining $first or $second. This third one calls undef $first inside the loop, its memory does not grow.

$ perl -wlE 'for (1..100_000) { my($first, $second); $first = sub { $second->() }; $second = sub { $first->() }; undef $first; } say "Done"; sleep 1000'
like image 58
Schwern Avatar answered Sep 21 '22 14:09

Schwern