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

My object contains references to other objects that it uses during its lifespan. When DESTROY happens, I often find that those other objects have already been GC'ed, and hence are no longer available (and I have to recontruct them to complete my tasks).

for example:

sub new { # typical bless invocation stuff here. $self->{_obj} = new Special::Object; return $self; } sub DESTROY { my $self = shift; $self->{_obj}->do_stuff(); # boom! Special::Object already GC'ed }

Is there a way to either ensure that my object is DESTROY'ed first, before the others I make use of?

Replies are listed 'Best First'.
Re: How can I call other objects from my object during DESTROY
by chromatic (Archbishop) on Jul 11, 2001 at 02:57 UTC
    Two options come to mind. Either or neither may be useful. First, make sure you destroy all references to your object before you exit. If you can keep track of that, great. (Perl 5.6 has support for weak references that can help.)

    Otherwise, you might have to add a separate not-quite-destructor phase. In Jellybean, anything that needs to shut down cleanly is expected to have a method named _cleanup(). When the Engine shuts down, it calls _cleanup() on the parent Container. Containers call this on all of their children.

    If we could count on an order of destruction at the end of the program, this wouldn't be necessary. It's a small price to pay for reliable semantics, though.

(tye)Re: How can I call other objects from my object during DESTROY
by tye (Sage) on Jul 11, 2001 at 02:13 UTC

    I consider this to be a bug in Perl's "garbage collection", specifically the omniously named "global destruction" phase. During that phase, all remaining objects are destroying in a "convenient and efficient" order. That is, reference counts are ignored here.

    So don't put any of your objects into global variables to avoid this problem.

    Update: A pragma to request an extra step just before "global destruction" that would decrement the ref counts of all global variables by one and do any destruction trigger by that would also solve the problem with minimal impact to the Perl world.

            - tye (but my friends call me "Tye")

      A number of people have suggested to "not put my objects into global variables" or to "use my()". Where am I putting Special::Object into a global variable? My module returns a blessed hashref, which itself contains (a reference to) another object. While someone seemed to claim that my case wouldn't happen, I most certainly have code in which it does (i.e. when object A's DESTROY is called, object B has already been DESTROY'ed and thus is no longer available to call methods on).

      Thanks for clearing up the confusion.

      -Aaron

        Where is the reference being stored after it is returned from the function? If you are writing a module that is used by others and they are reporting the errors, then you have a worse problem. You can note in the documentation for your module that they should not put your objects into global variables, but that may not be acceptable.

        You might want to post a working example that reproduces the problem.

        My Win32::TieRegistry has this problem. I was lucky in that the consequences of it are relatively minor once I got rid of the ugly warning that it was producing (some Registry keys won't get "auto unloaded" in rare cases, but this feature isn't used much anyway). So I didn't really solve the problem.

        To really solve it you could require perl 5.6 for your module and do something similar to what chromatic suggested. You create "weak back links" that run in the opposite direction to your references and have the subordinate object's destructor tell the parent object to "clean up" (if the parent hasn't already been DESTROYed). And the parent object has to be able to handle either being told to "clean up" first (if the subordinate gets DESTROYed first) or just being told to DESTROY (if the parent gets DESTROYed first).

        I'd rather have a "fix" for Perl because working around this can get really, really ugly.

                - tye (but my friends call me "Tye")

        I think what isn't clear is what you are doing with the blessed hashref to your object itself. To wit, if your object (the one your original post referred to) were an Xyz, then into what kind of variable are you putting the return from Xyz::new()?

        my $xyz = new Xyz();

        Or,

        $XYZ = new xyz(); # where $XYZ is a package global

        If you are using the latter (for example in a test harness program), you might want to test the former to see if it exhibits the same behavior you originally asked about.

Re: How can I call other objects from my object during DESTROY
by VSarkiss (Monsignor) on Jul 11, 2001 at 03:25 UTC

    My parsimonious friend tye above has hit the nail on the head, but you may still be wondering what's going on. Let me expand a bit.

    Perl reclaims memory in two distinct ways. First, it maintains a reference count, meaning it knows how many ways there are in the current program to refer to a given object. When the reference count goes to zero, the memory can be reclaimed.

    With this style, the problem outlined in your code sample won't happen. The instance of Special::Object won't be reclaimed as long as the instance of the containing class refers to it via $self->{_obj} (as long as the parent instance is around and nobody tampers with encapsulation).

    When a thread ends (or a single-threaded Perl exits), the so-called "global destruction" phase tries to reclaim memory allocated in that thread by traversing everything allocated in that thread. That's the stage at which your problem is happening. Now, the section on Two-Phased Garbage Collection states:

    Objects are destructed in a separate pass before ordinary refs just to prevent object destructors from using refs that have been themselves destructed.
    But as you've found, that doesn't solve the whole problem.

    At this point, your main option is algorithm changes. One way, as tye is suggesting, is to not make objects global. Another possibility is to remove inter-object dependencies as far as destruction is concerned (may be difficult depending on the rest of your code).

    HTH

    Update: Egads! I have to learn to type faster.

      Regarding:

      Objects are destructed in a separate pass before ordinary refs just to prevent object destructors from using refs that have been themselves destructed.
      That just means that blessed references are destroyed (in no particular order) before unblessed references. So that only helps if object X has an unblessed reference to something, not if object X has a reference to another object.

              - tye (but my friends call me "Tye")
Re: How can I call other objects from my object during DESTROY
by bikeNomad (Priest) on Jul 11, 2001 at 02:57 UTC
    Depending on what you're doing, you might either make sure that your variables are gone before global destruction (by, for instance, making them all in an inner lexical scope), or maybe to just ignore doing certain cleanup during global destruction. You can tell if you're in global destruction using this trick:

    package MyStuff; my $ending = 0; sub END { $ending = 1 } sub DESTROY { return if $ending; # more cleanup here }

    Of course, it depends on what kind of cleanup you need to do; writing to files, etc. would not be something you'd want to skip, while you could probably skip closing them.

      Last time I tested, just using lexical variables (my), even if they were at "file scope" was enough to prevent this problem.

              - tye (but my friends call me "Tye")
Re: How can I call other objects from my object during DESTROY
by jmcnamara (Monsignor) on Jul 11, 2001 at 11:56 UTC

    I think that this is the result of a bug. See the reference to a bug fix in the DESTROY order sequence from the following P5P summary.

    I also faced this problem with the Spreadsheet::WriteExcel module when I needed the objects to be DESTROYed in a fixed sequence. I worked around it by having the main container object call destructor methods in the contained objects via DESTROY. This generally works if the scope of the main object has been specified via my(). For cases where it doesn't work I supplied a explicit close() method in the main class and documented it's use.

    Nevertheless, I not completely happy with this behaviour and my workaround feels like a kludge.

    John.
    --