http://www.perlmonks.org?node_id=295214

Beechbone has asked for the wisdom of the Perl Monks concerning the following question:

Ok, I got a very easy problem---some of my objects are destroyed in the global cleanup phase, not when I undef their parent objects.

I already checked all global variables, they're not guilty. I also checked for module scoped 'my' variables, no luck. I checked the parameters and local variables of the run() method, they're clean, too.

But still there are (at least) two objects that are destroyed to late. And, they're not just being destroyed to late, their DESTROY() also fails, leaving orphan locks in the database.

So, my main question is: Is there any module that can actively search for references for a given classname? Or just search for all CVs that still exist and give their content and owner?

Search, Ask, Know
  • Comment on Help me to find hidden object references....

Replies are listed 'Best First'.
Re: Help me to find hidden object references....
by tachyon (Chancellor) on Sep 30, 2003 at 10:37 UTC

    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

Re: Help me to find hidden object references....
by bart (Canon) on Sep 30, 2003 at 11:23 UTC
    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.

Re: Help me to find hidden object references....
by RMGir (Prior) on Sep 30, 2003 at 10:09 UTC
    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.

    --
    Mike
Re: Help me to find hidden object references....
by Beechbone (Friar) on Sep 30, 2003 at 11:34 UTC
    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... ;)

    Search, Ask, Know