It sounds as though you are depending on DESTROY being called when you undef an object so that it releases a DB handle? Destroy will only be called if there are no references left to that object as you seem to know but even then from my understanding of Perls lazy garbage collection there is no guarantee.
It sounds a though you are talking mod_perl??? (global cleanup phase) - is that so? Perl does some things behind the scenes for efficiency that don't help your case.
Perhaps a better way to go is simply to call a finish/eof/disconnect method on your object to get the behaviour you want when you want it, rather than depending on DESTROY, prsumbably when the object goes out of scope. If you are actively using undef why not call an appropriate finish() method?
There is a Devel::Refcount module but it is not on CPAN. Look here for the XS and PM source code. h2xs -A -n Devel::Refcount will write you a stub distro to copy these files into so you can just do the usual install.
package Test;
$|++;
print "Creation\n";
my $obj = new Foo;
my $a = $obj; # comment in and out to see behavour change
print "Undef....\n";
undef $obj;
print "We have undefed!\n" unless defined $obj;
print "Sleep....\n";
sleep 2;
END{ print "Exiting\n" }
package Foo;
sub new { bless {}, shift };
sub DESTROY { print "Destroy.....\n" }
__END__
Creation
Undef....
We have undefed!
Sleep....
Destroy.....
Exiting
cheers
tachyon
s&&rsenoyhcatreve&&&s&n.+t&"$'$`$\"$\&"&ee&&y&srve&&d&&print
| [reply] [d/l] [select] |
Look for circular references, meaning: the children holding a reference to their parent, themselves, each other, .... You seem to be describing the typical symptoms.
If you find them, weaken these references with weaken() from either WeakRef or from Scalar::Util, depending on the version and compilation status of the latter — the pure perl version won't contain it. That way, the backreferences won't keep the object they're referring to, alive. Make sure you don't weaken the important, forward references. | [reply] |
Interesting problem... You've already checked globals, you say, so Devel::FindGlobals won't help.
I guess you've already put some print statements in the DESTROY routines, which is how you know when they're being cleared up?
If that's the case, then I'd suggest printing out the call stack and $self when you construct all the objects of that class, and then print out $self in DESTROY and compare to find out when the "guilty" references are created.
As an alternative, you could add a copy of the caller results from the constructor to your object, and then print it out in DESTROY to cut down on the noise.
That should help you track down the issue, I hope. I know you were looking for a module, but I hope this does the trick.
| [reply] |
Thank you both, but I found it in the meantime... (Took me almost 10 hours, all together.)
Very tricky, I think, that bugs---yes more than one was needed... I think I'll give a little bit of pseudo code:
class god;
method fork_kids {
my $row = $db->find_new_process_table_entries();
if (not fork()) {
my $runner = runner->new($row); #1
$runner->run();
}
}
class runner;
method new {
$self->{row} = shift;
}
method run {
$man = thaw $self->{row}->{column};
$man->run($self);
%{$man} = %{$self} = (); #2
exit();
}
method saveState() {
my $state = shift;
$db->saveState( nfreeze($state) );
$self->{row}->{state} = $state; #3
}
class man {
method run {
my $mom = shift;
while ($alive) {
$mom->saveState($state);
...
}
}
There is a forking server (no mod_perl, but same idea) called 'god', it watches the process table for new entries. When it finds one, it fork()s into a runner class. The single parameter for the runner is the database row (#1). The runner creates a $self, thaw()s the process (class man) from a BLOB and starts it. The process will be able to call back the runner to do some work (same idea as $r in mod_perl, again), e.g. save it's state. After the process has ended (maybe with an exception, or because it was killed or commanded to take a nap), the runner will clear all local variables to trigger all DESTROY()s of all objects that the process might have created (#2).
Now, there are 2 problems: First, see code #1: The runner gets the database row as array ref and saves that into $self later. Outch, $p will never go out of scape before exit(). This would not be a problem, as $row should only contain strings from the database, but: #3 the second bug. Here the saveState() method tries to hold the row in self in sync with the database. But, oops, it saves the real object, not the frozen one. And it is saved into the imortal $row.
And so all objects that were part of the state lived much longer than they should.
Yes, we rely on DESTROY() to clean up some objects' database "shadow". One very big reason: We also use exceptions. That's it.
I knew the createon time and place of all objects. I knew every single object in my test case. I just could not figure out why the did not die when I killed the "man" object, and I was really puzzled when I "killed" the state object... ;)
| [reply] [d/l] |
| [reply] |