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

Fellow monks

I am working on a project that requires that I code a destructive iterator to ensure certain cleanup happens. Unfortunately I am getting the destructor called prematurely and trying to see how this happens.

In case it is a bug with the instance, this is on Ubuntu Precise, with perl 5.14.2.

Below is my code. It seems pretty straight-forward to me:

use 5.010;

package it;

sub new {
    my $self = {};
    $self->{_queue} = 1 .. 10;
    bless $self;
}

sub current {
    my $self = shift;
    return $self->{_current};
}

sub next {
    my $self = shift;
    $self->current->destroy if eval { $self->current->can('destroy') };
    delete $self->{_current};
    return unless  @{$self->{_queue}};
    $self->{_current} = it_item->new(shift @{$self->{_queue}}) ;
    return $self->current;
}

sub DESTROY {
    say 'all done';
}

package it_item;

sub new {
    my ($cls, $item) = @_;
    my $self = { _item => $item };
    bless $self, $cls;
}

sub val {
    my ($self) = @_;
    return $self->{_item};
}

sub DESTROY {
    $self = shift;
    warn $self->{_item} . ' destroyed';
}

package main;

my $it = it->new();
# works as expected
$it->next;
$it->next;
while ($it->next){ say $it->current->val };

I would expect to see

1 destroyed
2 destroyed
3 destroyed
4 destroyed
...
all done

But what I actually see is very different

1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
3
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
4
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
5
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
6
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
7
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
8
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
9
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
10
1 destroyed at destroytest.pl line 44.
1 destroyed at destroytest.pl line 44.
all done
1 destroyed at destroytest.pl line 44 during global destruction.
DESTROY created new reference to dead object 'it_item' during global destruction.

Any idea why?

  • Comment on why am I getting odd behavior on DESTROY

Replies are listed 'Best First'.
Re: why am I getting odd behavior on DESTROY
by Corion (Pope) on Mar 20, 2015 at 09:39 UTC

    Adding

    use strict;

    after

    package it_item;

    will make Perl point out your error.

    You (re)use a global variable $self in various places in that package.

    This is actually a point where the new signatures feature (only available in 5.20+) could be useful as it makes it easier to avoid accidentially using global variables. On the other hand, you can still forget to list $self in the formal parameters of your subroutine...

      As a note, if anyone needs an example of why use strict is so important even for little things, this is a pretty good example and I will not be unhappy if you share it as an example of what not to do.

        As a quick write-up as to why this happens and why use strict helps here:

        The destructor reassigns the garbage-collected object to a global package variable, whichof course cancels the destruction. The next one gets copied over the existing package variable, which of course throws the first object back into garbage collection, which gets copied over the package variable, etc. in a loop.

        What is suprising here si that Perl doesn't go into an endless loop. Naturally this is hard to debug using standard tools because it is not a misnamed variable but a side effect of a scoping error. Strict prevents this sort of error.

        Again, anyone wants to use it for presentations or anything regarding why strict, feel free.

        If I could triple ++ this post I would. Out with the Ego, in with the Honest Man.

      Right. Teach me to say "just a proof of concept, not going to worry about use strict and use warnings...." :-P