views:

283

answers:

1

How do you completely delete a package in Perl? This means not only the package variables, but also any magic tables that Perl updates to handle inheritance changes and other things.

This simple test:

use warnings; use strict;
use Test::LeakTrace;
use Symbol 'delete_package';

leaktrace {
   package test;
   our $x = 1;

   package main;
   delete_package 'test';
};

results in the following output:

leaked ARRAY(0x81c930)  from /lib/perl5/5.10.1/Symbol.pm line 166.
leaked HASH(0x827760)   from /lib/perl5/5.10.1/Symbol.pm line 166.
leaked SCALAR(0x821920) from /lib/perl5/5.10.1/Symbol.pm line 166.

Using the -verbose flag for leaktrace results in screenfuls of data which I can post on request.

Things get worse if the line our @ISA = 'main'; is added to the test package:

leaked ARRAY(0x81cd10) from so.pl line 32.
leaked SCALAR(0x81c930) from so.pl line 32.
leaked ARRAY(0x8219d0) from so.pl line 32.
leaked HASH(0x8219c0) from so.pl line 32.
leaked SCALAR(0x8219b0) from so.pl line 32.
leaked HASH(0x8219a0) from so.pl line 32.
leaked SCALAR(0x821970) from /lib/perl5/5.10.1/Symbol.pm line 161.
leaked HASH(0x821950) from so.pl line 32.
leaked SCALAR(0x821940) from so.pl line 32.

Line 32 is where the our @ISA is.

To illustrate that these are indeed leaks and not just noise from the interpreter:

my $num = 0;
while (1) {
    no strict 'refs';
    @{$num.'::ISA'} = 'main';
    delete_package $num++;
}

will eat memory at a constant rate

So, is there a better way to get rid of a package than Symbol's delete_package? Is there something else that I have to do to help it along?

I have seen the same behavior in 5.8.8, 5.10.1, and 5.12

+4  A: 

So this is a bug in perl, a reported one even as you discovered. Short of fixing that, it seems like your only way to avoid these leaks is to chose another approach to solve your problem.

How come you need a semi-anonymous package instead of, for example, a closure? Those are easy enough to make not leak, and, with some creativity, you can still implement pretty much every external interface on top of them, for example by blessing your closure coderefs and provide methods for them, providing overloading for them, etc.

rafl
Due to the way these objects are used (tight inner loop calculations), I am trying to avoid multiple levels of redirection. So `curse` installs its objects into a package so that you can call `$obj->method` (and retain some semblance of inheritance) rather than exposing the implementation: `$$obj{method}()`. I could use `AUTOLOAD` or installation of stub methods into a parent class, but in each case it would at at least 1 additional subroutine call, and at least one hash lookup per call.
Eric Strom
I don't think semi-anonymous packages are a good way to optimise this sort of thing. For one, method calls, even as perl caches the method resolution after the first call, are a bit slower than plain calls to a coderef. Additionally, passing arguments to a function is measurably slower than building a coderef that closes over the arguments it needs. And when i said "object", i also didn't mean you using `$$obj{method}()`, but blessing the coderef itself. Also, using `AUTOLOAD` at all seems a bad way of optimising tight inner loops - it's **slow**.
rafl
`AUTOLOAD` and stub methods are already ruled out due to the reasons mentioned in my first comment. However, I still need to provide some sort of interface to the end user since there are many methods that can be called on an object. The fastest (runtime, not creation time) method I've found is to install the closure based methods into anon packages and then provide a reference blessed into that package. take a look at `List::Gen::curse` and the implementation of it's generators for an example. if you have a better way to optimize it let me know
Eric Strom
`s/would at/would add/` in the last sentence of my first comment.
Eric Strom
FWIW, this leak is fixed in http://perl5.git.perl.org/perl.git/commit/c8bbf675c3e9277e1dd4b1185d91c1aef2cd2594
rafl