Durch einen Kollegen bin ich auf ein Problem aufmerksam geworden, was ein verwirrendes Verhalten von globalen Variablen in Verbindung mit DESTROY beschreibt. Man nehme folgenden Code und betrachte speziell den unteren eval-Block:
use Test::More tests => 1;
{
package Foo;
sub new {
bless([])
}
sub DESTROY {
eval {
die('destroy');
}
}
sub bar {
die('bar');
}
}
eval {
my $foo = Foo->new();
$foo->bar();
};
main::diag($@);
in $@ sollte eigentlich sowas wie bar at Test.pm line x erscheinen, aber er ergibt destroy at…. Was ist also passiert? Es ist, wie in der „Fehlerbeschreibung“ erwähnt eigentlich kein Fehler, sondern eine Nebenwirkung davon, dass in Perl fast alles mit globalen Variablen gelöst wird. Wenn man den Code ein wenig erweitert, sieht man deutlich, was passiert:
#!perl -T
use Test::More tests => 1;
{
package Foo;
sub new {
main::diag('new() called');
bless([])
}
sub DESTROY {
main::diag('DESTROY() called');
eval {
die('destroy');
}
}
sub bar {
main::diag('bar() called');
die('bar');
}
}
main::diag('eval-START');
eval {
my $foo = Foo->new();
$foo->bar();
}; # HERE!
main::diag('eval-END');
like($@, qr/^destroy/, 'croak in DESTROY()');
Folgende Ausgabe wird erzeugt:
# eval-START
# new() called
# bar() called
# DESTROY() called
# eval-END
Zwischen dem Aufruf von bar() und eval-end mogelt sich das DESTROY frech dazwischen. Es gibt hier eine Nebenläufigkeit, da $foo am Ende des Eval-Blockes nicht mehr existiert und der GarbadgeCollector artig DESTROY() auf das Objekt aufruft. Da es aber nur eine Kopie von $@ gibt, wird der Inhalt durch das DESTROY-eval{} einfach überschrieben, denn das soll eval{} bei jedem Aufruf ja auch tun.
In der „Fehlerbeschreibung“ ist als mögliche Lösung angegeben, man soll immer local verwenden, um z.B. $@ nicht zu überschreiben. Um auf „Hat Fehler“ zu prüfen würde ich lieber ein
my $ok = eval {
#...some code
return 1;
};
verwenden, da man da erst gar nicht auf $@ angewiesen ist. Der Inhalt von $@ wird damit aber immer noch überschrieben werden.
Dieses Verhalten hat mir mal wieder gezeigt, dass mit allen Spezialmethoden von Perl, also import(), BEGIN, CHECK, END, INIT, DESTROY() und alle anderen, die zu bestimmten Ereignissen aufgerufen werden, immer sehr umsichtig umgegagnen werden muss. Am besten viele Diagnosemeldungen ausgeben lassen, damit man nicht den Überblick verliert.