Better Code Through Destruction
by Igor Gariev
|
Pages: 1, 2, 3
Finally, you can destroy any data structure, not just objects, if you provide code to do so. Pass in a subroutine reference or an anonymous subroutine:
##
## An unblessed data structure with circular references
## that cannot untangle itself.
##
use Object::Destroyer 2.0;
while (1) {
my (%a, %b);
$a{b} = \%b;
$b{a} = \%a;
my $sentry = Object::Destroyer->new( sub { undef $a{b} } );
}
Just for fun, comment out the line with the $sentry object and watch the memory consumption of the running script.
Using Object::Destroyer As a Wrapper
Object::Destroyer can make life easier for module authors, too.
If you have written a library with circular references, you may ask your clients to explicitly call a disposal method or use a new feature of Perl (stable since 5.8; see Scalar::Util)--weak references. Weak references do not increment reference counts of the objects to which they refer, so the Perl garbage collector can collect the referents. In the tree example, all references from leaves to parents (but not vice versa, or the tree will be lost!) may be weak. When the final reference to the root node goes away, Perl will dispose of it, which will remove its references to all of its children recursively. They will all reach zero, and Perl will reclaim them all down the branches of the tree to every leaf.
Indeed, some CPAN modules use this approach (XML::Twig). However, this solution works only if weak refs are available; this is certainly not the case for older Perl. Secondly, this may require quite a bit of rewriting (there are nine calls to weaken throughout the code of XML::Twig 3.26).
Alternatively, you may use Object::Destroyer internally in your library code. It can work as an almost transparent wrapper around your object:
##
## Object::Destroyer as a wrapper
##
package My::Tree;
use Object::Destroyer 2.0;
sub new {
my $class = shift;
my $self = bless {}, $class;
$self->populate;
return Object::Destroyer->new( $self, 'release' );
}
sub release{
## actual memory release code
}
sub say_hello{
my $self = shift;
print "Hello, I'm object of class ", ref($self), "\n";
}
package main;
{
my $tree = My::Tree->new;
$tree->say_hello;
##
## $tree->release will be called by Object::Destroyer;
##
}
The object $tree in the client code is actually an Object::Destroyer object that dispatches all invoked methods to the underlying object of class My::Tree. The method say_hello sees no difference at all--it receives an original $self object. Changes to code are minimal and well localized.
The approach has a limitation, too: clients must not access attributes of the object directly (such as $tree->{age}). This is a bad practice in client code anyway. Additionally, there is a small time penalty for method calls by client-side code. Calls made from the library code itself are not affected.
Exceptions and Resource Deallocation
Resource acquisition is initialization is a powerful technique to apply to the management of various critical resources, not only memory. It is most useful when using exceptions to handle errors. This combination makes code quite reliable: exceptions separate normal execution logic and error handling, and RAII sentries guarantee the correct release of every sensitive resource.
Consider alarms as an example. Assume that you have to call some potentially long-running (or even never-ending) code. You don't want your script to hang up, and prefer to break its execution. Alarms are just right for the task. However, the first attempt at good code might be awkward:
##
## Alarm example 1. Naive.
##
eval{
local $SIG{ALRM} = sub { die "Timed out\n" };
alarm(5);
long_running_code();
## Cancel the alarm if code returned within 5 sec.
alarm(0);
};
if ($@ && $@ eq "Timed out\n") {
## Process the error here
}
This code will work fine until long_running_code() dies. In this case, the eval block will catch the die, but not the alarm. If this occurred in a program that must run 24 hours a day, the program would end in 5 seconds.
This next example is much better; actually it is real-world code. It is enough for many applications. However, it's not completely bulletproof either:
##
## Alarm example 2. A standard solution.
##
eval{
local $SIG{ALRM} = sub { die "Timed out\n" };
alarm(5);
long_running_code();
## Cancel the alarm if long_running_code() returns within 5 sec.
alarm(0);
};
## Cancel the alarm if the long_running_code() died.
alarm(0);
How many times will the alarm be cancelled in the following example?
##
## Alarm example 3. Malicious code.
##
LOOP:
foreach my $arg (1..3) {
eval{
local $SIG{ALRM} = sub { die "Timed out\n" };
alarm(5);
long_running_code($arg);
alarm(0);
};
alarm(0);
}
sub long_running_code{ last LOOP; }
Oops, none.
The RAII solution is more reliable:
##
## Alarm example 4.
## Resource is under control of Object::Destroyer
##
eval{
local $SIG{ALRM} = sub { die "Timed out\n" };
alarm(5);
my $sentry = Object::Destroyer->new( sub {alarm(0)} );
long_running_code();
};
No matter how the code exits the eval block, Perl will destroy the $sentry object. That destruction will call alarm(0).

